From 486bfc17d1f9da19d72d3a19ff463b1eb8ad5595 Mon Sep 17 00:00:00 2001 From: nullishamy Date: Sat, 5 Apr 2025 13:31:32 +0100 Subject: [PATCH] feat: initial commit --- .envrc | 1 + .gitignore | 1 + config/lxd.nix | 1 + config/nixos.nix | 79 ++++++++++++ config/opengist.yml | 118 +++++++++++++++++ config/services/opengist.nix | 43 +++++++ flake.lock | 243 +++++++++++++++++++++++++++++++++++ flake.nix | 46 +++++++ secrets/atticd.env.age | Bin 0 -> 4688 bytes secrets/default.nix | 9 ++ secrets/secrets.nix | 10 ++ 11 files changed, 551 insertions(+) create mode 100644 .envrc create mode 100644 .gitignore create mode 100644 config/lxd.nix create mode 100644 config/nixos.nix create mode 100644 config/opengist.yml create mode 100644 config/services/opengist.nix create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 secrets/atticd.env.age create mode 100644 secrets/default.nix create mode 100644 secrets/secrets.nix diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9b42106 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.direnv/ diff --git a/config/lxd.nix b/config/lxd.nix new file mode 100644 index 0000000..6462967 --- /dev/null +++ b/config/lxd.nix @@ -0,0 +1 @@ +{...}: {} diff --git a/config/nixos.nix b/config/nixos.nix new file mode 100644 index 0000000..25997a4 --- /dev/null +++ b/config/nixos.nix @@ -0,0 +1,79 @@ +{ modulesPath, pkgs, config, ... }: + +{ + imports = [ + # Include the default lxd configuration. + "${modulesPath}/virtualisation/proxmox-lxc.nix" + # Include the container-specific autogenerated configuration. + ./lxd.nix + ./services/opengist.nix + ]; + + networking = { + dhcpcd.enable = false; + useDHCP = false; + useHostResolvConf = false; + firewall.enable = false; + }; + + environment.systemPackages = with pkgs; [ + git + curl + vim + ]; + + services.opengist = { + enable = true; + config = ./opengist.yml; + }; + + services.atticd = { + enable = true; + + # Replace with absolute path to your environment file + environmentFile = config.age.secrets."atticd.env".path; + + settings = { + listen = "0.0.0.0:8080"; + + jwt = { }; + + # Data chunking + # + # Warning: If you change any of the values here, it will be + # difficult to reuse existing chunks for newly-uploaded NARs + # since the cutpoints will be different. As a result, the + # deduplication ratio will suffer for a while after the change. + chunking = { + # The minimum NAR size to trigger chunking + # + # If 0, chunking is disabled entirely for newly-uploaded NARs. + # If 1, all NARs are chunked. + nar-size-threshold = 64 * 1024; # 64 KiB + + # The preferred minimum size of a chunk, in bytes + min-size = 16 * 1024; # 16 KiB + + # The preferred average size of a chunk, in bytes + avg-size = 64 * 1024; # 64 KiB + + # The preferred maximum size of a chunk, in bytes + max-size = 256 * 1024; # 256 KiB + }; + }; + }; + + systemd.network = { + enable = true; + networks."50-eth0" = { + matchConfig.Name = "eth0"; + networkConfig = { + DHCP = "ipv4"; + IPv6AcceptRA = true; + }; + linkConfig.RequiredForOnline = "routable"; + }; + }; + + system.stateVersion = "24.11"; # Did you read the comment? +} diff --git a/config/opengist.yml b/config/opengist.yml new file mode 100644 index 0000000..2a3945f --- /dev/null +++ b/config/opengist.yml @@ -0,0 +1,118 @@ +# Learn more about Opengist configuration here: +# https://github.com/thomiceli/opengist/blob/master/docs/configuration/configure.md +# https://github.com/thomiceli/opengist/blob/master/docs/configuration/cheat-sheet.md + +# Set the log level to one of the following: debug, info, warn, error, fatal. Default: warn +log-level: info + +# Set the log output to one or more of the following: `stdout`, `file`. Default: stdout,file +log-output: stdout,file + +# Public URL to access to Opengist +external-url: + +# Directory where Opengist will store its data. Default: ~/.opengist/ +opengist-home: /opt/opengist/data + +# Secret key used for session store & encrypt MFA data on database. Default: +secret-key: + +# URI of the database. Default: opengist.db (SQLite) is placed in opengist-home +# SQLite: file:/path/to/database +# PostgreSQL: postgres://user:password@host:port/database +# MySQL/MariaDB: mysql://user:password@host:port/database +db-uri: opengist.db + +# Enable or disable the code search index (either `true` or `false`). Default: true +index.enabled: true + +# Name of the directory where the code search index is stored. Default: opengist.index +index.dirname: opengist.index + +# Default branch name used by Opengist when initializing Git repositories. +# If not set, uses the Git default branch name. See https://git-scm.com/book/en/v2/Getting-Started-First-Time-Git-Setup#_new_default_branch +git.default-branch: + +# Set the journal mode for SQLite. Default: WAL +# See https://www.sqlite.org/pragma.html#pragma_journal_mode +# For SQLite databases only. +sqlite.journal-mode: WAL + +# HTTP server configuration +# Host to bind to. Default: 0.0.0.0 +http.host: 0.0.0.0 + +# Port to bind to. Default: 6157 +http.port: 6157 + +# Enable or disable git operations (clone, pull, push) via HTTP (either `true` or `false`). Default: true +http.git-enabled: true + +# SSH built-in server configuration +# Note: it is not using the SSH daemon from your machine (yet) + +# Enable or disable SSH built-in server +# for git operations (clone, pull, push) via SSH (either `true` or `false`). Default: true +ssh.git-enabled: false + +# Host to bind to. Default: 0.0.0.0 +ssh.host: 0.0.0.0 + +# Port to bind to. Default: 2222 +# Note: it cannot be the same port as the SSH daemon if it's currently running +# If you want to use the port 22 for the built-in SSH server, +# you can either change the port of the SSH daemon or stop it +ssh.port: 2222 + +# Public domain for the Git SSH connection, if it has to be different from the HTTP one. +# If not set, uses the URL from the request +ssh.external-domain: + +# Path or alias to ssh-keygen executable. Default: ssh-keygen +ssh.keygen-executable: ssh-keygen + +# OAuth2 configuration +# The callback/redirect URL must be http://opengist.url/oauth//callback + +# To create a new OAuth2 application using GitHub : https://github.com/settings/applications/new +github.client-key: +github.secret: + +# To create a new OAuth2 application using Gitlab : https://gitlab.com/-/user_settings/applications +gitlab.client-key: +gitlab.secret: +# URL of the Gitlab instance. Default: https://gitlab.com/ +gitlab.url: https://gitlab.com/ +# The name of the GitLab instance. It is displayed in the OAuth login button. Default: GitLab +gitlab.name: GitLab + +# To create a new OAuth2 application using Gitea : https://gitea.domain/user/settings/applications +gitea.client-key: +gitea.secret: +# URL of the Gitea instance. Default: https://gitea.com/ +gitea.url: https://gitea.com/ +# The name of the Gitea instance. It is displayed in the OAuth login button. Default: Gitea +gitea.name: Gitea + +# To create a new OAuth2 application using OpenID Connect: +oidc.client-key: +oidc.secret: +# Discovery endpoint of the OpenID provider. Generally something like http://auth.example.com/.well-known/openid-configuration +oidc.discovery-url: + +# Instance name +# Set your own custom name to be displayed instead of 'Opengist' +custom.name: + +# Custom assets +# Add your own custom assets, that are files relatives to $opengist-home/custom/ +custom.logo: +custom.favicon: + +# Static pages in footer (like legal notices, privacy policy, etc.) +# The path can be a URL or a relative path to a file in the $opengist-home/custom/ directory +custom.static-links: +# - name: Gitea +# path: https://gitea.com +# - name: Legal notices +# path: legal.html diff --git a/config/services/opengist.nix b/config/services/opengist.nix new file mode 100644 index 0000000..db7bd1c --- /dev/null +++ b/config/services/opengist.nix @@ -0,0 +1,43 @@ +{ config, lib, pkgs, ... }: + +with lib; # use the functions from lib, such as mkIf + +let + cfg = config.services.opengist; + src = builtins.fetchTarball { + url = "https://github.com/thomiceli/opengist/releases/download/v1.9.1/opengist1.9.1-linux-amd64.tar.gz"; + sha256 = "sha256:0cayri7yz792964mq3h52dryjs7rjn3xhw5papi589c8d9a0afw4"; + }; +in { + options = { + services.opengist = { + enable = mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable OpenGist. + ''; + }; + + config = mkOption { + type = types.path; + description = '' + The config path to use. + ''; + }; + }; + }; + + config = mkIf cfg.enable { + systemd.services.opengist = { + path = [ + pkgs.git + pkgs.openssh + ]; + name = "opengist.service"; + enable = true; + script = "${src}/opengist --config ${cfg.config}"; + description = "OpenGist"; + }; + }; +} diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..9fdbea0 --- /dev/null +++ b/flake.lock @@ -0,0 +1,243 @@ +{ + "nodes": { + "agenix": { + "inputs": { + "darwin": "darwin", + "home-manager": "home-manager", + "nixpkgs": "nixpkgs", + "systems": "systems" + }, + "locked": { + "lastModified": 1736955230, + "narHash": "sha256-uenf8fv2eG5bKM8C/UvFaiJMZ4IpUFaQxk9OH5t/1gA=", + "owner": "ryantm", + "repo": "agenix", + "rev": "e600439ec4c273cf11e06fe4d9d906fb98fa097c", + "type": "github" + }, + "original": { + "owner": "ryantm", + "repo": "agenix", + "type": "github" + } + }, + "darwin": { + "inputs": { + "nixpkgs": [ + "agenix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1700795494, + "narHash": "sha256-gzGLZSiOhf155FW7262kdHo2YDeugp3VuIFb4/GGng0=", + "owner": "lnl7", + "repo": "nix-darwin", + "rev": "4b9b83d5a92e8c1fbfd8eb27eda375908c11ec4d", + "type": "github" + }, + "original": { + "owner": "lnl7", + "ref": "master", + "repo": "nix-darwin", + "type": "github" + } + }, + "deploy-rs": { + "inputs": { + "flake-compat": "flake-compat", + "nixpkgs": "nixpkgs_2", + "utils": "utils" + }, + "locked": { + "lastModified": 1727447169, + "narHash": "sha256-3KyjMPUKHkiWhwR91J1YchF6zb6gvckCAY1jOE+ne0U=", + "owner": "serokell", + "repo": "deploy-rs", + "rev": "aa07eb05537d4cd025e2310397a6adcedfe72c76", + "type": "github" + }, + "original": { + "owner": "serokell", + "repo": "deploy-rs", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1696426674, + "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-parts": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib" + }, + "locked": { + "lastModified": 1743550720, + "narHash": "sha256-hIshGgKZCgWh6AYJpJmRgFdR3WUbkY04o82X05xqQiY=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "c621e8422220273271f52058f618c94e405bb0f5", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "home-manager": { + "inputs": { + "nixpkgs": [ + "agenix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1703113217, + "narHash": "sha256-7ulcXOk63TIT2lVDSExj7XzFx09LpdSAPtvgtM7yQPE=", + "owner": "nix-community", + "repo": "home-manager", + "rev": "3bfaacf46133c037bb356193bd2f1765d9dc82c1", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "home-manager", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1703013332, + "narHash": "sha256-+tFNwMvlXLbJZXiMHqYq77z/RfmpfpiI3yjL6o/Zo9M=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "54aac082a4d9bb5bbc5c4e899603abfb76a3f6d6", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-lib": { + "locked": { + "lastModified": 1743296961, + "narHash": "sha256-b1EdN3cULCqtorQ4QeWgLMrd5ZGOjLSLemfa00heasc=", + "owner": "nix-community", + "repo": "nixpkgs.lib", + "rev": "e4822aea2a6d1cdd36653c134cacfd64c97ff4fa", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nixpkgs.lib", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1702272962, + "narHash": "sha256-D+zHwkwPc6oYQ4G3A1HuadopqRwUY/JkMwHz1YF7j4Q=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "e97b3e4186bcadf0ef1b6be22b8558eab1cdeb5d", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_3": { + "locked": { + "lastModified": 1743583204, + "narHash": "sha256-F7n4+KOIfWrwoQjXrL2wD9RhFYLs2/GGe/MQY1sSdlE=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "2c8d3f48d33929642c1c12cd243df4cc7d2ce434", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "agenix": "agenix", + "deploy-rs": "deploy-rs", + "flake-parts": "flake-parts", + "nixpkgs": "nixpkgs_3" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "utils": { + "inputs": { + "systems": "systems_2" + }, + "locked": { + "lastModified": 1701680307, + "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..379d4d4 --- /dev/null +++ b/flake.nix @@ -0,0 +1,46 @@ +{ + description = "straight up nixing it"; + + inputs = { + flake-parts.url = "github:hercules-ci/flake-parts"; + + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + deploy-rs.url = "github:serokell/deploy-rs"; + agenix.url = "github:ryantm/agenix"; + }; + + outputs = inputs@{ flake-parts, self, ... }: + flake-parts.lib.mkFlake { inherit inputs; } { + systems = [ "x86_64-linux" "aarch64-linux" "aarch64-darwin" "x86_64-darwin" ]; + perSystem = { config, self', inputs', pkgs, system, ... }: { + devShells.default = pkgs.mkShell { + packages = with pkgs; [ + deploy-rs + inputs'.agenix.packages.default + ]; + }; + }; + + flake = { + nixosConfigurations.nixos = inputs.nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + modules = [ + ./config/nixos.nix + ./secrets + inputs.agenix.nixosModules.default + ]; + }; + + deploy.nodes.nixos = { + hostname = "nixos.cluster"; + profiles.system = { + sshUser = "root"; + path = inputs.deploy-rs.lib.x86_64-linux.activate.nixos self.nixosConfigurations.nixos; + }; + }; + + # This is highly advised, and will prevent many possible mistakes + checks = builtins.mapAttrs (system: deployLib: deployLib.deployChecks self.deploy) inputs.deploy-rs.lib; + }; + }; +} diff --git a/secrets/atticd.env.age b/secrets/atticd.env.age new file mode 100644 index 0000000000000000000000000000000000000000..bdf798f24bf51db92a4f606b05919def92dc6824 GIT binary patch literal 4688 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 igz8>AuUEKk|J>$9?