Compare commits
4 commits
2a40712346
...
cc887fdc72
| Author | SHA1 | Date | |
|---|---|---|---|
| cc887fdc72 | |||
| 2bd18ab7ca | |||
| a8e9884c3c | |||
| e65718178e |
16 changed files with 88 additions and 633 deletions
40
README.md
40
README.md
|
|
@ -6,32 +6,20 @@ Declaratively keep your stuff up to date in your nixos config.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
```nix
|
```nix
|
||||||
{
|
services.fooud = {
|
||||||
description = "My flake config";
|
enable = true;
|
||||||
inputs.fooud.url = "git+https://git.squi.bid/squibid/fooud";
|
repos = [
|
||||||
outputs = { self, nixpkgs, fooud }: {
|
{
|
||||||
nixosConfigurations.my-system = nixpkgs.lib.nixosSystem {
|
path = "/full/path/to/your/repo.git";
|
||||||
modules = [{
|
hooks = [
|
||||||
services.nginx.virtualHosts."squi.bid" = {
|
pkgs.writeScriptBin "post-recieve" ''
|
||||||
root = fooud.lib.gitUpdater config {
|
git clone . /var/www/your/deployed/location
|
||||||
git = "https://git.squi.bid/squibid/squi.bid"; # the source of the data
|
'';
|
||||||
dest = "/var/www/squi.bid"; # where should the files live on disk
|
];
|
||||||
keys = [ "BECE5684D3C4005D" ]; # requires the commit to be signed by me
|
}
|
||||||
check = "5m"; # we may be no more than 5 minutes out of date from the source
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
locations."/" = {
|
|
||||||
tryFiles = "$uri $uri.html $uri/";
|
|
||||||
index = "index.html index.htm";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
# TODO
|
# TODO
|
||||||
- extract archives
|
- [ ] add support for non-git files
|
||||||
- support copying files from other parts of the filesystem (although that's rathar impure isn't it)
|
|
||||||
- add some checks
|
|
||||||
|
|
|
||||||
42
build.zig
42
build.zig
|
|
@ -1,42 +0,0 @@
|
||||||
const std = @import("std");
|
|
||||||
|
|
||||||
pub fn build(b: *std.Build) void {
|
|
||||||
const target = b.standardTargetOptions(.{});
|
|
||||||
const optimize = b.standardOptimizeOption(.{});
|
|
||||||
|
|
||||||
const mod = b.addModule("fooud", .{
|
|
||||||
.root_source_file = b.path("src/main.zig"),
|
|
||||||
.target = target,
|
|
||||||
});
|
|
||||||
|
|
||||||
const exe = b.addExecutable(.{
|
|
||||||
.name = "fooud",
|
|
||||||
.root_module = b.createModule(.{
|
|
||||||
.root_source_file = b.path("src/main.zig"),
|
|
||||||
.target = target,
|
|
||||||
.optimize = optimize,
|
|
||||||
.imports = &.{
|
|
||||||
.{ .name = "fooud", .module = mod },
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
const dep_curl = b.dependency("curl", .{});
|
|
||||||
|
|
||||||
exe.root_module.addImport("curl", dep_curl.module("curl"));
|
|
||||||
exe.root_module.linkSystemLibrary("git2", .{});
|
|
||||||
exe.root_module.linkSystemLibrary("gpgme", .{});
|
|
||||||
exe.root_module.addIncludePath(b.path("src/util"));
|
|
||||||
exe.root_module.addCSourceFile(.{ .file = b.path("src/util/gpg_helper.c") });
|
|
||||||
exe.linkLibC();
|
|
||||||
|
|
||||||
b.installArtifact(exe);
|
|
||||||
|
|
||||||
const run_step = b.step("run", "Run the app");
|
|
||||||
const run_cmd = b.addRunArtifact(exe);
|
|
||||||
run_step.dependOn(&run_cmd.step);
|
|
||||||
run_cmd.step.dependOn(b.getInstallStep());
|
|
||||||
if (b.args) |args| {
|
|
||||||
run_cmd.addArgs(args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
.{
|
|
||||||
.name = .fooud,
|
|
||||||
.version = "1.0.0",
|
|
||||||
.fingerprint = 0xe809364a41c00a2b, // Changing this has security and trust implications.
|
|
||||||
.minimum_zig_version = "0.15.2",
|
|
||||||
.dependencies = .{
|
|
||||||
.curl = .{
|
|
||||||
.url = "https://github.com/jiacai2050/zig-curl/archive/refs/tags/v0.3.2.zip",
|
|
||||||
.hash = "curl-0.3.2-P4tT4SXPAACuV6f5eyh4jG_1SspjWwMm_vRJfoKrQep5",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
.paths = .{
|
|
||||||
"build.zig",
|
|
||||||
"build.zig.zon",
|
|
||||||
"src",
|
|
||||||
// For example...
|
|
||||||
//"LICENSE",
|
|
||||||
//"README.md",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
# generated by zon2nix (https://github.com/nix-community/zon2nix)
|
|
||||||
|
|
||||||
{ linkFarm, fetchzip, fetchgit }:
|
|
||||||
|
|
||||||
linkFarm "zig-packages" [
|
|
||||||
{
|
|
||||||
name = "N-V-__8AAFrtpQI1j9eOv7aN3lt3eH1TJfH4npAdRIrg2gGH";
|
|
||||||
path = fetchzip {
|
|
||||||
url = "https://github.com/Mbed-TLS/mbedtls/archive/refs/tags/v3.6.0.tar.gz";
|
|
||||||
hash = "sha256-yzGBkrqh+T/5GS66xL5zJstCmvcfG09TfxqA3F8UPJg=";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
{
|
|
||||||
name = "N-V-__8AAHipPQF9UuLPiaV1CtJzZIxvTN61tMGdFx8LGjIV";
|
|
||||||
path = fetchzip {
|
|
||||||
url = "https://github.com/curl/curl/releases/download/curl-8_8_0/curl-8.8.0.tar.gz";
|
|
||||||
hash = "sha256-Gqfe8iiC8aCBiuUVO6VYqW5DmqgSv5oS4XvMfQgbwFw=";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
{
|
|
||||||
name = "N-V-__8AAJj_QgDBhU17TCtcvdjOZZPDfkvxrEAyZkc14VN8";
|
|
||||||
path = fetchzip {
|
|
||||||
url = "https://github.com/madler/zlib/releases/download/v1.3.1/zlib-1.3.1.tar.gz";
|
|
||||||
hash = "sha256-acY8yFzIRYbrZ2CGODoxLnZuppsP6KZy19I9Yy77pfc=";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
{
|
|
||||||
name = "curl-0.3.2-P4tT4SXPAACuV6f5eyh4jG_1SspjWwMm_vRJfoKrQep5";
|
|
||||||
path = fetchzip {
|
|
||||||
url = "https://github.com/jiacai2050/zig-curl/archive/refs/tags/v0.3.2.zip";
|
|
||||||
hash = "sha256-1FjYirex2Q/zs5GLNtCGgClyV5/SW0GhxFHA1Hm+e4o=";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
]
|
|
||||||
13
flake.lock
generated
13
flake.lock
generated
|
|
@ -2,18 +2,17 @@
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1764733908,
|
"lastModified": 1767892417,
|
||||||
"narHash": "sha256-QJiih52NU+nm7XQWCj+K8SwUdIEayDQ1FQgjkYISt4I=",
|
"narHash": "sha256-dhhvQY67aboBk8b0/u0XB6vwHdgbROZT3fJAjyNh5Ww=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "cadcc8de247676e4751c9d4a935acb2c0b059113",
|
"rev": "3497aa5c9457a9d88d71fa93a4a8368816fbeeba",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "NixOS",
|
"id": "nixpkgs",
|
||||||
"ref": "nixpkgs-unstable",
|
"ref": "nixos-unstable",
|
||||||
"repo": "nixpkgs",
|
"type": "indirect"
|
||||||
"type": "github"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
|
|
|
||||||
63
flake.nix
63
flake.nix
|
|
@ -1,66 +1,11 @@
|
||||||
{
|
{
|
||||||
description = "Declaratively update your data.";
|
description = "Declaratively update your data.";
|
||||||
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
inputs.nixpkgs.url = "nixpkgs/nixos-unstable";
|
||||||
outputs = { self, nixpkgs, ... }: let
|
outputs = { nixpkgs, ... }: {
|
||||||
system = "x86_64-linux";
|
lib = builtins.import ./lib.nix { pkgs = nixpkgs; };
|
||||||
pkgs = import nixpkgs { inherit system; };
|
|
||||||
package = pkgs.stdenv.mkDerivation rec {
|
|
||||||
pname = "fooud";
|
|
||||||
version = "1.0";
|
|
||||||
|
|
||||||
src = ./.;
|
|
||||||
deps = pkgs.callPackage ./build.zig.zon.nix {};
|
|
||||||
nativeBuildInputs = [
|
|
||||||
pkgs.zig.hook
|
|
||||||
pkgs.glibc
|
|
||||||
pkgs.libgit2
|
|
||||||
pkgs.gpgme
|
|
||||||
pkgs.libgpg-error
|
|
||||||
pkgs.pkg-config
|
|
||||||
];
|
|
||||||
zigBuildFlags = [
|
|
||||||
"--system" "${deps}"
|
|
||||||
# "-Doptimize=Debug"
|
|
||||||
# "-Dtarget=${system}"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
in {
|
|
||||||
lib = builtins.import ./lib.nix { pkgs = pkgs; };
|
|
||||||
nixosModules = rec {
|
nixosModules = rec {
|
||||||
fooud = { pkgs, lib, config, inputs, ... }: {
|
fooud = builtins.import ./module.nix;
|
||||||
options.programs.fooud.enable = lib.mkEnableOption ("fooud")
|
|
||||||
// { default = true; };
|
|
||||||
config = lib.mkIf config.programs.fooud.enable {
|
|
||||||
environment.systemPackages = [
|
|
||||||
pkgs.glibc
|
|
||||||
pkgs.libgit2
|
|
||||||
pkgs.gpgme
|
|
||||||
pkgs.libgpg-error
|
|
||||||
pkgs.nix
|
|
||||||
package
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
default = fooud;
|
default = fooud;
|
||||||
};
|
};
|
||||||
packages.${system} = rec { default = package; fooud = default; };
|
|
||||||
|
|
||||||
checks.${system}.build = let
|
|
||||||
package = self.packages.${system}.default;
|
|
||||||
in pkgs.runCommand "fooud-build" { buildInputs = [
|
|
||||||
pkgs.git
|
|
||||||
pkgs.nix
|
|
||||||
]; } ''
|
|
||||||
mkdir repo
|
|
||||||
git -C repo init > /dev/null 2>&1
|
|
||||||
echo "hi" > repo/README.md
|
|
||||||
git -C repo config user.email "you@example.com" > /dev/null 2>&1
|
|
||||||
git -C repo config user.name "Your Name" > /dev/null 2>&1
|
|
||||||
git -C repo add . > /dev/null 2>&1
|
|
||||||
git -C repo commit -m "initial commit" > /dev/null 2>&1
|
|
||||||
|
|
||||||
# this check won't succeed until I find a way to run a chroot store
|
|
||||||
${package}/bin/fooud --git repo --dest test
|
|
||||||
'';
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
68
lib.nix
68
lib.nix
|
|
@ -1,72 +1,12 @@
|
||||||
{ pkgs, ... }:
|
{ pkgs, ... }:
|
||||||
let
|
let
|
||||||
defaultUpdater = { git ? null, url ? null, path ? null, keys ? null, dest, check, config }:
|
post-recv = dest: pkgs.writeScriptBin "post-recv" ''
|
||||||
let
|
git clone . ${dest}
|
||||||
config.systemd.services."fooud-${dest}" = {
|
|
||||||
serviceConfig = {
|
|
||||||
Type = "oneshot";
|
|
||||||
User = "root";
|
|
||||||
Group = "root";
|
|
||||||
ExecStart = let
|
|
||||||
dest = pkgs.lib.assertMsg dest "dest must be set";
|
|
||||||
remote =
|
|
||||||
if git then "--git " + git
|
|
||||||
else if url then "--url " + url
|
|
||||||
else if path then "--path " + path
|
|
||||||
else builtins.throw "one of git, url or path must be set";
|
|
||||||
keys_str = if git then
|
|
||||||
pkgs.lib.strings.concatStrings builtins.map (x: "--key ${x} ") keys
|
|
||||||
else throw "cannot use keys with git";
|
|
||||||
in pkgs.writeShellScript "fooud-${dest}-wrapper" ''
|
|
||||||
${pkgs.fooud}/bin/fooud ${keys_str} ${remote} ${dest}
|
|
||||||
'';
|
'';
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
config.systemd.timers."fooud-${dest}" = {
|
|
||||||
wantedBy = [ "timers.target" ];
|
|
||||||
timerConfig = {
|
|
||||||
OnActiveSec = "0s";
|
|
||||||
OnUnitActiveSec = check;
|
|
||||||
Unit = "fooud-${dest}.service";
|
|
||||||
Persistent = true;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
in
|
|
||||||
{
|
|
||||||
config = config;
|
|
||||||
dest = dest;
|
|
||||||
};
|
|
||||||
in
|
in
|
||||||
let
|
let
|
||||||
gitUpdater = config: { git, keys, dest, check }:
|
lib.git = {
|
||||||
(defaultUpdater {
|
inherit post-recv;
|
||||||
git = git;
|
|
||||||
keys = keys;
|
|
||||||
dest = dest;
|
|
||||||
check = check;
|
|
||||||
config = config;
|
|
||||||
}).dest;
|
|
||||||
fsUpdater = config: { path, dest, check }:
|
|
||||||
(defaultUpdater {
|
|
||||||
path = path;
|
|
||||||
dest = dest;
|
|
||||||
check = check;
|
|
||||||
config = config;
|
|
||||||
}).dest;
|
|
||||||
urlUpdater = config: { url, dest, check }:
|
|
||||||
(defaultUpdater {
|
|
||||||
url = url;
|
|
||||||
dest = dest;
|
|
||||||
check = check;
|
|
||||||
config = config;
|
|
||||||
}).dest;
|
|
||||||
|
|
||||||
lib = {
|
|
||||||
inherit
|
|
||||||
gitUpdater
|
|
||||||
fsUpdater
|
|
||||||
urlUpdater;
|
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
lib
|
lib
|
||||||
|
|
|
||||||
59
module.nix
Normal file
59
module.nix
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
{ config, pkgs, lib, ... }:
|
||||||
|
{
|
||||||
|
options.services.fooud = {
|
||||||
|
enable = lib.mkEnableOption config.description;
|
||||||
|
repos = lib.mkOption {
|
||||||
|
type = lib.listOf {
|
||||||
|
path = lib.mkOption {
|
||||||
|
description = "fullpath to the repositiory on your server";
|
||||||
|
type = lib.types.string;
|
||||||
|
};
|
||||||
|
hooks = lib.mkOption {
|
||||||
|
type = lib.listOf lib.types.path;
|
||||||
|
example = [
|
||||||
|
pkgs.writeScriptBin "post-recieve" ''
|
||||||
|
git clone . /var/www/your/deployed/location
|
||||||
|
''
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
user = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "fooud-deploy";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = let
|
||||||
|
cfg = config.services.fooud;
|
||||||
|
in lib.mkIf cfg.enable {
|
||||||
|
environment.systemPackages = with pkgs; [ git ];
|
||||||
|
users.users."${cfg.user}" = lib.mkIf cfg.user != "root" {
|
||||||
|
group = "${cfg.user}";
|
||||||
|
isSystemUser = true;
|
||||||
|
createHome = true;
|
||||||
|
home = "/var/lib/${cfg.user}";
|
||||||
|
shell = "${pkgs.git}/bin/git-shell";
|
||||||
|
};
|
||||||
|
users.groups."${cfg.user}" = {};
|
||||||
|
|
||||||
|
systemd = {
|
||||||
|
services."fooud" = {
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
User = cfg.user;
|
||||||
|
ExecStart = pkgs.writeScriptBin "fooud-deploy"
|
||||||
|
(lib.concatMapStrings
|
||||||
|
(repo: ''
|
||||||
|
if [ -d ${repo.path} ]; then
|
||||||
|
rm -f ${repo.path}/hooks/*
|
||||||
|
cp ${repo.hooks}/bin/* ${repo.name}/hooks/
|
||||||
|
fi
|
||||||
|
'')
|
||||||
|
cfg.repos);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
const NixStore = @This();
|
|
||||||
|
|
||||||
const std = @import("std");
|
|
||||||
|
|
||||||
const gpa = std.heap.page_allocator;
|
|
||||||
|
|
||||||
/// Add a path to the store, this will copy the contents of path recursively
|
|
||||||
/// into the store and return a (hopefully) valid store path. To try and keep
|
|
||||||
/// this store path valid you should follow this with a call to realize() and
|
|
||||||
/// then root the store path.
|
|
||||||
pub fn add(path: []const u8) error{Failure}![]const u8 {
|
|
||||||
const res = std.process.Child.run(.{
|
|
||||||
.allocator = gpa,
|
|
||||||
.argv = &[_][]const u8{ "nix", "store", "add", path },
|
|
||||||
}) catch return error.Failure;
|
|
||||||
|
|
||||||
if (res.term != .Exited or res.term.Exited != 0) return error.Failure;
|
|
||||||
if (res.stdout.len == 0) return error.Failure;
|
|
||||||
return res.stdout[0 .. res.stdout.len - 1]; // to chop off the \n
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This tries to tell the store that a store_path should stick around a while
|
|
||||||
/// longer, there's a chance that it doesn't listen and removes that store_path
|
|
||||||
/// but in my testing it seemed to stick around.
|
|
||||||
pub fn realize(store_path: []const u8) error{Failure}!void {
|
|
||||||
_ = std.process.Child.run(.{
|
|
||||||
.allocator = gpa,
|
|
||||||
.argv = &[_][]const u8{ "nix-store", "--realize", store_path },
|
|
||||||
}) catch return error.Failure;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This tells the nix store to not gc our new store_path because it has a
|
|
||||||
/// dependency at root_path and that only if root_path doesn't exist anymore
|
|
||||||
/// it can delete store_path.
|
|
||||||
pub fn root(store_path: []const u8, root_path: []const u8) error{Failure}!void {
|
|
||||||
_ = std.process.Child.run(.{
|
|
||||||
.allocator = gpa,
|
|
||||||
.argv = &[_][]const u8{ "nix-store", "--add-root", root_path, "--indirect", store_path },
|
|
||||||
}) catch return error.Failure;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Delete a path from the store.
|
|
||||||
pub fn delete(store_path: []const u8) error{Failure}!void {
|
|
||||||
_ = std.process.Child.run(.{
|
|
||||||
.allocator = gpa,
|
|
||||||
.argv = &[_][]const u8{ "nix", "store", "delete", store_path },
|
|
||||||
}) catch {
|
|
||||||
return error.Failure;
|
|
||||||
};
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
const Extractor = @This();
|
|
||||||
|
|
||||||
// TODO: impl
|
|
||||||
153
src/main.zig
153
src/main.zig
|
|
@ -1,153 +0,0 @@
|
||||||
const std = @import("std");
|
|
||||||
|
|
||||||
const NixStore = @import("NixStore.zig");
|
|
||||||
const Git = @import("remotes/Git.zig");
|
|
||||||
const Curl = @import("remotes/Curl.zig");
|
|
||||||
const Fs = @import("remotes/Fs.zig");
|
|
||||||
|
|
||||||
const gpa = std.heap.page_allocator;
|
|
||||||
const Options = enum {
|
|
||||||
git,
|
|
||||||
url,
|
|
||||||
path,
|
|
||||||
dest,
|
|
||||||
key,
|
|
||||||
extract,
|
|
||||||
};
|
|
||||||
|
|
||||||
const Config = struct {
|
|
||||||
const Remote = union(enum) {
|
|
||||||
git: []const u8,
|
|
||||||
url: []const u8,
|
|
||||||
path: []const u8,
|
|
||||||
none,
|
|
||||||
};
|
|
||||||
remote: Remote,
|
|
||||||
dest: ?[]const u8,
|
|
||||||
keys: std.ArrayList([]const u8),
|
|
||||||
extract: bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn main() !void {
|
|
||||||
var config: Config = .{
|
|
||||||
.remote = .none,
|
|
||||||
.dest = null,
|
|
||||||
.keys = .empty,
|
|
||||||
.extract = false,
|
|
||||||
};
|
|
||||||
|
|
||||||
try check_args(&config);
|
|
||||||
|
|
||||||
const tmp_dest = try std.fmt.allocPrint(gpa, "/tmp/{s}", .{config.dest.?});
|
|
||||||
defer gpa.free(tmp_dest);
|
|
||||||
|
|
||||||
// we're gonna delete the temp file(s) if they exist because we don't want
|
|
||||||
// to deal with them
|
|
||||||
var exists = true;
|
|
||||||
std.fs.cwd().access(tmp_dest, .{}) catch {
|
|
||||||
exists = false;
|
|
||||||
};
|
|
||||||
if (exists) try std.fs.cwd().deleteTree(tmp_dest);
|
|
||||||
|
|
||||||
const res = try switch (config.remote) {
|
|
||||||
.git => Git.get(config.remote.git, tmp_dest, try config.keys.toOwnedSlice(gpa)),
|
|
||||||
.url => Curl.get(config.remote.url, tmp_dest),
|
|
||||||
.path => Fs.get(config.remote.path, tmp_dest),
|
|
||||||
else => unreachable,
|
|
||||||
};
|
|
||||||
defer std.fs.cwd().deleteTree(tmp_dest) catch {};
|
|
||||||
|
|
||||||
if (!res) {
|
|
||||||
std.log.err("failed to obtain the remote file(s)", .{});
|
|
||||||
std.process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.extract) {
|
|
||||||
// TODO: impl
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now that we've gotten all our files it's time to add it to the nix store
|
|
||||||
const store_path = try NixStore.add(tmp_dest);
|
|
||||||
try NixStore.realize(store_path);
|
|
||||||
std.log.info("new store path: {s}", .{store_path});
|
|
||||||
|
|
||||||
// link the dest to the new nix store path
|
|
||||||
var dest_old_store_path: ?[]u8 = null;
|
|
||||||
while (true) {
|
|
||||||
std.fs.cwd().symLink(store_path, config.dest.?, .{}) catch |err| switch (err) {
|
|
||||||
error.PathAlreadyExists => {
|
|
||||||
// get the old nix store path
|
|
||||||
const buf: [1024]u8 = undefined;
|
|
||||||
dest_old_store_path = try std.fs.cwd().readLink(config.dest.?, @constCast(buf[0..]));
|
|
||||||
std.log.info("old store path: {s}", .{dest_old_store_path.?});
|
|
||||||
|
|
||||||
// if the old store path and the current store path are the
|
|
||||||
// same then don't do anything, nothing will change
|
|
||||||
if (std.mem.eql(u8, dest_old_store_path.?, store_path)) return;
|
|
||||||
|
|
||||||
try std.fs.cwd().deleteFile(config.dest.?);
|
|
||||||
continue;
|
|
||||||
},
|
|
||||||
else => {
|
|
||||||
if (dest_old_store_path) |path| {
|
|
||||||
try std.fs.cwd().symLink(path, config.dest.?, .{});
|
|
||||||
}
|
|
||||||
return err;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
try NixStore.root(store_path, config.dest.?);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// delete the old nix store path
|
|
||||||
if (dest_old_store_path) |path| {
|
|
||||||
std.log.info("deleting old store path: {s}", .{path});
|
|
||||||
try NixStore.delete(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_args(config: *Config) !void {
|
|
||||||
const iter = @constCast(&std.process.args());
|
|
||||||
while (iter.next()) |arg| if (std.mem.eql(u8, arg[0..2], "--")) {
|
|
||||||
const t = std.meta.stringToEnum(Options, arg[2..]) orelse {
|
|
||||||
std.log.err("{s} is not a valid option", .{arg});
|
|
||||||
std.process.exit(1);
|
|
||||||
};
|
|
||||||
if (t == .extract) { // this option doesn't require arguments
|
|
||||||
config.extract = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const val = iter.next() orelse return error.Invalid;
|
|
||||||
switch (t) {
|
|
||||||
.git => config.remote = Config.Remote{ .git = val },
|
|
||||||
.url => config.remote = Config.Remote{ .url = val },
|
|
||||||
.path => config.remote = Config.Remote{ .path = val },
|
|
||||||
.key => try config.keys.append(gpa, val),
|
|
||||||
.dest => config.dest = val,
|
|
||||||
else => unreachable,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var err = false;
|
|
||||||
if (config.remote == .none) {
|
|
||||||
std.log.err("you must set one of --git, --url, or --path", .{});
|
|
||||||
err = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.dest == null) {
|
|
||||||
std.log.err("you must set a --dest", .{});
|
|
||||||
err = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.keys.items.len > 0 and config.remote != .git) {
|
|
||||||
std.log.err("you can only check keys on a git repository", .{});
|
|
||||||
err = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.extract and config.remote == .git) {
|
|
||||||
std.log.err("you cannot extract a git repository", .{});
|
|
||||||
err = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (err) std.process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
const Curl = @This();
|
|
||||||
|
|
||||||
const std = @import("std");
|
|
||||||
const curl = @import("curl");
|
|
||||||
|
|
||||||
const gpa = std.heap.page_allocator;
|
|
||||||
|
|
||||||
pub fn get(url: []const u8, dest: []const u8) !bool {
|
|
||||||
const ca_bundle = try curl.allocCABundle(gpa);
|
|
||||||
defer ca_bundle.deinit();
|
|
||||||
const easy = try curl.Easy.init(.{ .ca_bundle = ca_bundle });
|
|
||||||
defer easy.deinit();
|
|
||||||
|
|
||||||
// let's get that dest file opened up
|
|
||||||
const fp = try std.fs.cwd().createFile(dest, .{});
|
|
||||||
defer fp.close();
|
|
||||||
const buffer: [1024]u8 = undefined;
|
|
||||||
const writer = fp.writer(@constCast(&buffer));
|
|
||||||
|
|
||||||
var tmp_url = try gpa.alloc(u8, url.len + 1);
|
|
||||||
defer gpa.free(tmp_url);
|
|
||||||
@memcpy(tmp_url[0..url.len], url);
|
|
||||||
tmp_url[url.len] = 0;
|
|
||||||
|
|
||||||
// download it
|
|
||||||
try easy.setUrl(@ptrCast(tmp_url));
|
|
||||||
try easy.setWriter(@constCast(&writer.interface));
|
|
||||||
const response = try easy.perform();
|
|
||||||
if (response.status_code != 200) return false;
|
|
||||||
|
|
||||||
try @constCast(&writer.interface).flush();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
const Fs = @This();
|
|
||||||
|
|
||||||
const std = @import("std");
|
|
||||||
|
|
||||||
pub fn get(src: []const u8, dest: []const u8) !bool {
|
|
||||||
_ = src;
|
|
||||||
_ = dest;
|
|
||||||
|
|
||||||
// TODO: impl
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
@ -1,117 +0,0 @@
|
||||||
const Git = @This();
|
|
||||||
|
|
||||||
const std = @import("std");
|
|
||||||
pub const c = @cImport({
|
|
||||||
@cInclude("git2.h");
|
|
||||||
@cInclude("gpgme.h");
|
|
||||||
@cInclude("gpg_helper.h");
|
|
||||||
});
|
|
||||||
|
|
||||||
pub const GitError = error{
|
|
||||||
InitFailed,
|
|
||||||
OpenFailed,
|
|
||||||
NotFound,
|
|
||||||
InvalidObject,
|
|
||||||
InvalidReference,
|
|
||||||
OutOfMemory,
|
|
||||||
Unknown,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn get(url: []const u8, dest: []const u8, keys: []const []const u8) !bool {
|
|
||||||
if (c.git_libgit2_init() < 0) return GitError.InitFailed;
|
|
||||||
defer _ = c.git_libgit2_shutdown();
|
|
||||||
|
|
||||||
var exists = true;
|
|
||||||
std.fs.cwd().access(dest, .{}) catch {
|
|
||||||
exists = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
var repo: ?*c.git_repository = null;
|
|
||||||
if (!exists) repo = clone(url, dest) catch return false;
|
|
||||||
defer if (repo) |r| c.git_repository_free(r);
|
|
||||||
|
|
||||||
if (keys.len <= 0) return repo != null;
|
|
||||||
return try check_signing_key(repo, keys);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clone(url: []const u8, dest: []const u8) !?*c.git_repository {
|
|
||||||
var clone_opts: c.git_clone_options = undefined;
|
|
||||||
_ = c.git_clone_options_init(&clone_opts, c.GIT_CLONE_OPTIONS_VERSION);
|
|
||||||
var repo: ?*c.git_repository = null;
|
|
||||||
|
|
||||||
const err = c.git_clone(&repo, url.ptr, dest.ptr, &clone_opts);
|
|
||||||
if (err != 0) {
|
|
||||||
const git_err = c.git_error_last();
|
|
||||||
if (git_err != null and git_err.*.message != null) {
|
|
||||||
std.log.err("Clone error {d}: {s}", .{ err, git_err.*.message });
|
|
||||||
} else {
|
|
||||||
std.log.err("Clone error {d}: <unknown>", .{err});
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return repo;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_signing_key(repo: ?*c.git_repository, keys: []const []const u8) !bool {
|
|
||||||
var head_ref: ?*c.git_reference = null;
|
|
||||||
if (c.git_repository_head(&head_ref, repo) != 0) {
|
|
||||||
std.log.err("Failed to get HEAD", .{});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
defer c.git_reference_free(head_ref);
|
|
||||||
|
|
||||||
const oid_ptr = c.git_reference_target(head_ref);
|
|
||||||
if (oid_ptr == null) {
|
|
||||||
std.log.err("HEAD is not pointing to a commit", .{});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var signature = c.git_buf{};
|
|
||||||
var signed_data = c.git_buf{};
|
|
||||||
if (c.git_commit_extract_signature(&signature, &signed_data, repo, @constCast(oid_ptr), null) != 0) {
|
|
||||||
std.log.err("HEAD commit is not signed", .{});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
defer c.git_buf_dispose(&signature);
|
|
||||||
defer c.git_buf_dispose(&signed_data);
|
|
||||||
|
|
||||||
// gpg me
|
|
||||||
|
|
||||||
_ = c.gpgme_check_version(null);
|
|
||||||
var ctx: c.gpgme_ctx_t = null;
|
|
||||||
_ = c.gpgme_new(&ctx);
|
|
||||||
defer _ = c.gpgme_release(ctx);
|
|
||||||
|
|
||||||
var sig_data: c.gpgme_data_t = null;
|
|
||||||
var signed_text_data: c.gpgme_data_t = null;
|
|
||||||
_ = c.gpgme_data_new_from_mem(&sig_data, signature.ptr, signature.size, 0);
|
|
||||||
defer _ = c.gpgme_data_release(sig_data);
|
|
||||||
_ = c.gpgme_data_new_from_mem(&signed_text_data, signed_data.ptr, signed_data.size, 0);
|
|
||||||
defer _ = c.gpgme_data_release(signed_text_data);
|
|
||||||
|
|
||||||
if (c.gpgme_op_verify(ctx, sig_data, signed_text_data, null) != 0) {
|
|
||||||
std.log.err("Failed to verify signature", .{});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = c.gpgme_op_verify_result(ctx);
|
|
||||||
if (result == null) {
|
|
||||||
std.log.err("No signature found in verification result", .{});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const fpr = c.first_signature_fpr(ctx);
|
|
||||||
if (fpr == null) {
|
|
||||||
std.log.err("No signature found", .{});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (keys) |key| {
|
|
||||||
if (std.mem.eql(u8, std.mem.span(fpr + 24), key)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
#include "gpg_helper.h"
|
|
||||||
|
|
||||||
const char* first_signature_fpr(gpgme_ctx_t ctx) {
|
|
||||||
gpgme_verify_result_t result = gpgme_op_verify_result(ctx);
|
|
||||||
if (!result || !result->signatures) return NULL;
|
|
||||||
return result->signatures->fpr;
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
#include <gpgme.h>
|
|
||||||
|
|
||||||
const char* first_signature_fpr(gpgme_ctx_t ctx);
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue