diff --git a/.sops.yaml b/.sops.yaml index d72a625..b93a6e2 100644 --- a/.sops.yaml +++ b/.sops.yaml @@ -4,7 +4,6 @@ keys: - &monolith-ssh age1ecyynwv93lfu7crjjp8l47defv07quzfzaktwurpep7jc9eha5pscg7lrw - &double-rainbow-ssh age1026d4c8nqyapcsy4jz57szt6zw3ejcgv3ecyvz0s89t7w7z964fqdqv52h - &phantom-ssh age1m4mqcd2kmuhfr8a22rvh02c68jkakhdfmuqgtusuv0czk4jvna7sz79p3y - - &stonehenge-ssh age13y65zemwlfnf5pszspeh87utv5jrfm35varxjdsh78xhfhs7la3scm9l9g creation_rules: - path_regex: secrets/[^/]+\.(yaml|json|env|ini|gpg)$ @@ -36,10 +35,3 @@ creation_rules: age: - *lelgenio-ssh - *phantom-ssh - - path_regex: secrets/stonehenge/[^/]+\.(yaml|json|env|ini|gpg)$ - key_groups: - - pgp: - - *lelgenio-gpg - age: - - *lelgenio-ssh - - *stonehenge-ssh diff --git a/flake.nix b/flake.nix index 76033c7..97c7a6e 100644 --- a/flake.nix +++ b/flake.nix @@ -190,16 +190,6 @@ ./hosts/phantom ]; }; - stonehenge = lib.nixosSystem { - inherit system specialArgs; - modules = [ - { nixpkgs.pkgs = pkgs; } - ./system/marge-bot - ./system/renovate-bot - ./hosts/stonehenge - inputs.sops-nix.nixosModules.default - ]; - }; }; homeConfigurations.lelgenio = inputs.home-manager.lib.homeManagerConfiguration { diff --git a/hosts/stonehenge/default.nix b/hosts/stonehenge/default.nix deleted file mode 100644 index 84ea856..0000000 --- a/hosts/stonehenge/default.nix +++ /dev/null @@ -1,155 +0,0 @@ -# Edit this configuration file to define what should be installed on -# your system. Help is available in the configuration.nix(5) man page -# and in the NixOS manual (accessible by running ‘nixos-help’). - -{ config, pkgs, ... }: - -{ - imports = [ - # Include the results of the hardware scan. - ./hardware-configuration.nix - ./gitlab-runner.nix - ./nebula-vpn.nix - ./vagrant.nix - - ./gitlab-marge-bot.nix - ./renovate-bot.nix - - ../../system/sops.nix - ../../system/nix.nix - ]; - - # Bootloader. - boot.loader.systemd-boot.enable = true; - boot.loader.efi.canTouchEfiVariables = true; - - networking.hostName = "stonehenge"; # Define your hostname. - # networking.wireless.enable = true; # Enables wireless support via wpa_supplicant. - - # Configure network proxy if necessary - # networking.proxy.default = "http://user:password@proxy:port/"; - # networking.proxy.noProxy = "127.0.0.1,localhost,internal.domain"; - - # Enable networking - networking.networkmanager.enable = true; - - # Set your time zone. - time.timeZone = "America/Sao_Paulo"; - - # Select internationalisation properties. - i18n.defaultLocale = "pt_BR.UTF-8"; - - i18n.extraLocaleSettings = { - LC_ADDRESS = "pt_BR.UTF-8"; - LC_IDENTIFICATION = "pt_BR.UTF-8"; - LC_MEASUREMENT = "pt_BR.UTF-8"; - LC_MONETARY = "pt_BR.UTF-8"; - LC_NAME = "pt_BR.UTF-8"; - LC_NUMERIC = "pt_BR.UTF-8"; - LC_PAPER = "pt_BR.UTF-8"; - LC_TELEPHONE = "pt_BR.UTF-8"; - LC_TIME = "pt_BR.UTF-8"; - }; - - # Enable the X11 windowing system. - # You can disable this if you're only using the Wayland session. - # services.xserver.enable = true; - - # Enable the KDE Plasma Desktop Environment. - services.displayManager.sddm.enable = true; - services.desktopManager.plasma6.enable = true; - - # Configure keymap in X11 - services.xserver.xkb = { - layout = "us"; - variant = "colemak"; - }; - - # Enable CUPS to print documents. - services.printing.enable = true; - - # Enable sound with pipewire. - services.pulseaudio.enable = false; - security.rtkit.enable = true; - services.pipewire = { - enable = true; - alsa.enable = true; - alsa.support32Bit = true; - pulse.enable = true; - # If you want to use JACK applications, uncomment this - #jack.enable = true; - - # use the example session manager (no others are packaged yet so this is enabled by default, - # no need to redefine it in your config for now) - #media-session.enable = true; - }; - - # Enable touchpad support (enabled default in most desktopManager). - # services.xserver.libinput.enable = true; - - # Define a user account. Don't forget to set a password with ‘passwd’. - users.users.user = { - isNormalUser = true; - description = "user"; - extraGroups = [ - "networkmanager" - "wheel" - "libvirtd" - "kvm" - ]; - packages = with pkgs; [ - # kdePackages.kate - # thunderbird - ]; - }; - - security.sudo.wheelNeedsPassword = false; - - virtualisation.virtualbox.host.enable = true; - - virtualisation.libvirtd.enable = true; - - # Install firefox. - programs.firefox.enable = true; - - # # Allow unfree packages - # nixpkgs.config.allowUnfree = true; - - # List packages installed in system profile. To search, run: - # $ nix search wget - environment.systemPackages = with pkgs; [ - # vim # Do not forget to add an editor to edit configuration.nix! The Nano editor is also installed by default. - # wget - dnsmasq - ]; - - networking.firewall.trustedInterfaces = [ "virbr0" ]; - - # Some programs need SUID wrappers, can be configured further or are - # started in user sessions. - # programs.mtr.enable = true; - # programs.gnupg.agent = { - # enable = true; - # enableSSHSupport = true; - # }; - - # List services that you want to enable: - - # Enable the OpenSSH daemon. - services.openssh.enable = true; - - # Open ports in the firewall. - # networking.firewall.allowedTCPPorts = [ ... ]; - # networking.firewall.allowedUDPPorts = [ ... ]; - # Or disable the firewall altogether. - # networking.firewall.enable = false; - - # This value determines the NixOS release from which the default - # settings for stateful data, like file locations and database versions - # on your system were taken. It‘s perfectly fine and recommended to leave - # this value at the release version of the first install of this system. - # Before changing this value read the documentation for this option - # (e.g. man configuration.nix or on https://nixos.org/nixos/options.html). - system.stateVersion = "25.11"; # Did you read the comment? - -} diff --git a/hosts/stonehenge/gitlab-marge-bot.nix b/hosts/stonehenge/gitlab-marge-bot.nix deleted file mode 100644 index c7b49b0..0000000 --- a/hosts/stonehenge/gitlab-marge-bot.nix +++ /dev/null @@ -1,41 +0,0 @@ -{ - config, - self, - pkgs, - ... -}: - -let - s = config.sops.secrets; - cfg = config.services.marge-bot; - - secretConfig = { - owner = cfg.user; - group = cfg.group; - sopsFile = ../../secrets/stonehenge/default.yaml; - }; -in -{ - services.marge-bot = { - enable = true; - package = self.packages.${pkgs.system}.marge-bot; - gitlabUrl = "https://gitlab.wopus.dev"; - authTokenFile = s."gitlab-marge-bot/token".path; - sshKeyFile = s."gitlab-marge-bot/ssh-secret-key".path; - settings = { - ci-timeout = "60min"; - add-part-of = true; - add-reviewers = true; - keep-reviewers = true; - keep-commits = true; - impersonate-approvers = true; - - batch = true; - use-no-ff-batches = true; - skip-ci-batches = true; - }; - }; - - sops.secrets."gitlab-marge-bot/token" = secretConfig; - sops.secrets."gitlab-marge-bot/ssh-secret-key" = secretConfig; -} diff --git a/hosts/stonehenge/gitlab-runner.nix b/hosts/stonehenge/gitlab-runner.nix deleted file mode 100644 index cde8cd7..0000000 --- a/hosts/stonehenge/gitlab-runner.nix +++ /dev/null @@ -1,36 +0,0 @@ -{ - config, - pkgs, - ... -}: -let - inherit (pkgs.callPackage ../../system/gitlab-runner.nix { }) mkNixRunnerFull; -in -{ - boot.kernel.sysctl."net.ipv4.ip_forward" = true; - virtualisation.docker.enable = true; - services.gitlab-runner = { - enable = true; - settings.concurrent = 1; - services = { - wopus-gitlab-nix = mkNixRunnerFull { - authenticationTokenConfigFile = config.sops.secrets."gitlab-runners/wopus-gitlab-nix".path; - # nixCacheSshPrivateKeyPath = config.sops.secrets."gitlab-runners/wopus-ssh-nix-cache-pk".path; - # nixCacheSshPublicKeyPath = config.sops.secrets."gitlab-runners/wopus-ssh-nix-cache-pub".path; - }; - }; - }; - systemd.services.gitlab-runner.serviceConfig.Nice = 10; - - sops.secrets = { - "gitlab-runners/wopus-gitlab-nix" = { - sopsFile = ../../secrets/stonehenge/default.yaml; - }; - "gitlab-runners/wopus-ssh-nix-cache-pk" = { - sopsFile = ../../secrets/stonehenge/default.yaml; - }; - "gitlab-runners/wopus-ssh-nix-cache-pub" = { - sopsFile = ../../secrets/stonehenge/default.yaml; - }; - }; -} diff --git a/hosts/stonehenge/hardware-configuration.nix b/hosts/stonehenge/hardware-configuration.nix deleted file mode 100644 index 192dd20..0000000 --- a/hosts/stonehenge/hardware-configuration.nix +++ /dev/null @@ -1,48 +0,0 @@ -# Do not modify this file! It was generated by ‘nixos-generate-config’ -# and may be overwritten by future invocations. Please make changes -# to /etc/nixos/configuration.nix instead. -{ - config, - lib, - pkgs, - modulesPath, - ... -}: - -{ - imports = [ - (modulesPath + "/installer/scan/not-detected.nix") - ]; - - boot.initrd.availableKernelModules = [ - "xhci_pci" - "ahci" - "usb_storage" - "usbhid" - "sd_mod" - ]; - boot.initrd.kernelModules = [ ]; - boot.kernelModules = [ ]; - boot.extraModulePackages = [ ]; - - fileSystems."/" = { - device = "/dev/disk/by-uuid/d22f00a1-af56-4468-a041-96523befe151"; - fsType = "ext4"; - }; - - fileSystems."/boot" = { - device = "/dev/disk/by-uuid/FE31-5AA5"; - fsType = "vfat"; - options = [ - "fmask=0077" - "dmask=0077" - ]; - }; - - swapDevices = [ - { device = "/dev/disk/by-uuid/533ceee7-721c-4bdc-9212-6043bf05b205"; } - ]; - - nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; - hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; -} diff --git a/hosts/stonehenge/nebula-vpn.nix b/hosts/stonehenge/nebula-vpn.nix deleted file mode 100644 index 6666fc4..0000000 --- a/hosts/stonehenge/nebula-vpn.nix +++ /dev/null @@ -1,61 +0,0 @@ -{ pkgs, config, ... }: -let - s = config.sops.secrets; - - secretConfig = { - owner = "nebula-wopus"; - group = "nebula-wopus"; - restartUnits = [ "nebula@wopus.service" ]; - sopsFile = ../../secrets/stonehenge/default.yaml; - }; -in -{ - environment.systemPackages = with pkgs; [ nebula ]; - - services.nebula.networks.wopus = { - enable = true; - isLighthouse = false; - lighthouses = [ - "192.168.88.1" - "192.168.88.2" - "192.168.88.3" - ]; - settings = { - cipher = "aes"; - }; - cert = s."nebula-wopus-vpn/stonehenge-crt".path; - key = s."nebula-wopus-vpn/stonehenge-key".path; - ca = s."nebula-wopus-vpn/ca-crt".path; - staticHostMap = { - "192.168.88.1" = [ - "neubla-vpn.wopus.dev:4242" - ]; - "192.168.88.2" = [ - "82.25.77.78:4242" - ]; - "192.168.88.3" = [ - "72.60.60.221:4242" - ]; - }; - firewall.outbound = [ - { - host = "any"; - port = "any"; - proto = "any"; - } - ]; - firewall.inbound = [ - { - host = "any"; - port = "any"; - proto = "any"; - } - ]; - }; - - sops.secrets = { - "nebula-wopus-vpn/ca-crt" = secretConfig; - "nebula-wopus-vpn/stonehenge-crt" = secretConfig; - "nebula-wopus-vpn/stonehenge-key" = secretConfig; - }; -} diff --git a/hosts/stonehenge/renovate-bot.nix b/hosts/stonehenge/renovate-bot.nix deleted file mode 100644 index 4b00939..0000000 --- a/hosts/stonehenge/renovate-bot.nix +++ /dev/null @@ -1,49 +0,0 @@ -{ config, pkgs, ... }: -let - cfg = config.services.renovate-bot; - s = config.sops.secrets; -in -{ - services.renovate-bot = { - enable = true; - schedule = "*-*-* *:00:00"; - logLevel = "info"; - - platform = "gitlab"; - endpoint = "https://gitlab.wopus.dev/api/v4"; - tokenFile = s."renovate-bot/token".path; - envFile = s."renovate-bot/env".path; - - extraPackages = with pkgs; [ - nodejs - rustc - cargo - php - phpPackages.composer - ]; - - settings = { - autodiscover = true; - labels = [ "renovate" ]; - rebaseWhen = "conflicted"; - - cacheDir = "/var/lib/renovate-bot/cache"; - persistRepoData = true; - prConcurrentLimit = 2; - branchConcurrentLimit = 2; - }; - }; - - sops.secrets."renovate-bot/token" = { - owner = cfg.user; - group = cfg.group; - mode = "0400"; - sopsFile = ../../secrets/stonehenge/default.yaml; - }; - sops.secrets."renovate-bot/env" = { - owner = cfg.user; - group = cfg.group; - mode = "0400"; - sopsFile = ../../secrets/stonehenge/default.yaml; - }; -} diff --git a/hosts/stonehenge/vagrant.nix b/hosts/stonehenge/vagrant.nix deleted file mode 100644 index 33ac64b..0000000 --- a/hosts/stonehenge/vagrant.nix +++ /dev/null @@ -1,57 +0,0 @@ -{ pkgs, ... }: -let - vagrantScript = pkgs.writeScriptBin "vagrant-vnode-05" '' - #!${pkgs.bash}/bin/bash - set -euo pipefail - - export PATH="${ - pkgs.lib.makeBinPath ( - with pkgs; - [ - vagrant - curl - openssh - virtualbox - ] - ) - }:$PATH" - export VNODE_NAME=vnode-05 - - cd /home/user/kubernetes-cluster/vnodes - exec ${pkgs.vagrant}/bin/vagrant up - ''; -in -{ - environment.systemPackages = with pkgs; [ - vagrant - curl - openssh - ]; - - users.users.user.extraGroups = [ "vboxusers" ]; - - systemd.services.vagrant-vnode-05 = { - description = "Vagrant vnode-05 service"; - after = [ "network-online.target" ]; - wants = [ "network-online.target" ]; - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = true; - ExecStart = "${vagrantScript}/bin/vagrant-vnode-05"; - User = "user"; - WorkingDirectory = "/home/user/kubernetes-cluster/vnodes"; - Environment = "PATH=${ - pkgs.lib.makeBinPath ( - with pkgs; - [ - vagrant - curl - openssh - virtualbox - ] - ) - }:$PATH"; - }; - wantedBy = [ "multi-user.target" ]; - }; -} diff --git a/pkgs/default.nix b/pkgs/default.nix index 17a6fc7..f33aa3f 100644 --- a/pkgs/default.nix +++ b/pkgs/default.nix @@ -7,7 +7,6 @@ rec { cargo-checkmate = pkgs.callPackage ./cargo-checkmate.nix { }; lipsum = pkgs.callPackage ./lipsum.nix { }; emmet-cli = pkgs.callPackage ./emmet-cli.nix { }; - marge-bot = pkgs.callPackage ./marge-bot { }; material-wifi-icons = pkgs.callPackage ./material-wifi-icons.nix { }; gnome-pass-search-provider = pkgs.callPackage ./gnome-pass-search-provider.nix { }; my-factorio-headless = pkgs.callPackage ./factorio-headless { diff --git a/pkgs/marge-bot/default.nix b/pkgs/marge-bot/default.nix deleted file mode 100644 index b240c40..0000000 --- a/pkgs/marge-bot/default.nix +++ /dev/null @@ -1,76 +0,0 @@ -{ - lib, - python3, - fetchFromGitLab, - fetchpatch, - git, - openssh, - nix-update-script, -}: - -python3.pkgs.buildPythonApplication rec { - pname = "marge-bot"; - version = "0.16.0"; - pyproject = true; - - src = fetchFromGitLab { - owner = "marge-org"; - repo = "marge-bot"; - rev = version; - hash = "sha256-UgdbeJegeTFP6YF6oMxAeQDI9AO2k6yk4WAFZ/Xspu8="; - }; - - patches = [ - # TODO: remove when merged https://gitlab.com/marge-org/marge-bot/-/merge_requests/571 - (fetchpatch { - url = "https://gitlab.com/marge-org/marge-bot/-/commit/7e9668b24455bcf9c99646853019a3e86505d850.patch"; - hash = "sha256-n5zB3YmD7i6RmcO9XmHjWVdQHzeVuZUPOzY3+cA6NDk="; - }) - ./patches/wait-for-mr-update.patch - ./patches/allow-self-merges.patch - ]; - - nativeBuildInputs = [ - python3.pkgs.setuptools - ]; - - propagatedBuildInputs = - (with python3.pkgs; [ - configargparse - maya - pyyaml - requests - python-gitlab - hatchling - ]) - ++ [ - git - openssh - ]; - - nativeCheckInputs = - (with python3.pkgs; [ - pytest-cov-stub - pytestCheckHook - pendulum - ]) - ++ [ - git - ]; - - pythonImportsCheck = [ "marge" ]; - - passthru.updateScript = nix-update-script { }; - - meta = with lib; { - description = "Merge bot for GitLab"; - homepage = "https://gitlab.com/marge-org/marge-bot"; - changelog = "https://gitlab.com/marge-org/marge-bot/-/blob/${src.rev}/CHANGELOG.md"; - license = licenses.bsd3; - maintainers = with maintainers; [ - bcdarwin - lelgenio - ]; - mainProgram = "marge.app"; - }; -} diff --git a/pkgs/marge-bot/patches/allow-self-merges.patch b/pkgs/marge-bot/patches/allow-self-merges.patch deleted file mode 100644 index 5ea4904..0000000 --- a/pkgs/marge-bot/patches/allow-self-merges.patch +++ /dev/null @@ -1,38 +0,0 @@ -diff --git i/marge/job.py w/marge/job.py -index ae707c0..404fb18 100644 ---- i/marge/job.py -+++ w/marge/job.py -@@ -616,8 +616,6 @@ def _get_reviewer_names_and_emails( - self_reviewed = {commit["author_email"] for commit in commits} & { - user.email for user in users - } -- if self_reviewed and len(users) <= 1: -- raise CannotMerge("Commits require at least one independent reviewer.") - return [f"{user.name} <{user.email}>" for user in users] - - -diff --git i/tests/test_approvals.py w/tests/test_approvals.py -index a65ae95..ecb38a4 100644 ---- i/tests/test_approvals.py -+++ w/tests/test_approvals.py -@@ -168,20 +168,6 @@ class TestApprovals: - commits=[], approvals=self.approvals, api=self.api - ) == ["Administrator ", "Roger Ebert "] - -- @patch("marge.user.User.fetch_by_id") -- def test_approvals_fails_when_same_author(self, user_fetch_by_id): -- info = dict(INFO, approved_by=list(INFO["approved_by"])) -- del info["approved_by"][1] -- approvals = Approvals(self.api, info) -- user_fetch_by_id.side_effect = lambda id, _: marge.user.User( -- self.api, USERS[id] -- ) -- commits = [{"author_email": "root@localhost"}] -- with pytest.raises(CannotMerge): -- _get_reviewer_names_and_emails( -- commits=commits, approvals=approvals, api=self.api -- ) -- - @patch("marge.user.User.fetch_by_id") - def test_approvals_succeeds_with_independent_author(self, user_fetch_by_id): - user_fetch_by_id.side_effect = lambda id, _: marge.user.User( diff --git a/pkgs/marge-bot/patches/wait-for-mr-update.patch b/pkgs/marge-bot/patches/wait-for-mr-update.patch deleted file mode 100644 index 4f4227c..0000000 --- a/pkgs/marge-bot/patches/wait-for-mr-update.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git i/marge/batch_job.py w/marge/batch_job.py -index b6423d8..3db302f 100644 ---- i/marge/batch_job.py -+++ w/marge/batch_job.py -@@ -205,6 +205,8 @@ class BatchMergeJob(job.MergeJob): - # Rebase and apply the trailers - self.update_merge_request(merge_request, source_repo_url=source_repo_url) - -+ time.sleep(10) -+ - # This switches git to - final_sha = self.merge_batch( - merge_request.target_branch, diff --git a/secrets/stonehenge/default.yaml b/secrets/stonehenge/default.yaml deleted file mode 100644 index 22e93c3..0000000 --- a/secrets/stonehenge/default.yaml +++ /dev/null @@ -1,54 +0,0 @@ -gitlab-runners: - wopus-gitlab-nix: ENC[AES256_GCM,data:u+FYWx3yluA+zFk8VV7RB4TW1AP81K8Ntgd7QDHwb2w0bzQH7URmfF1PrQgZGu/r5Q4zOFgmyUkL6EML9KFFu+3QpilIOTXitiEoi/McOn0DnAOTLhW1Fbg42jKd3gTU9OyLDijlQs3ktyRRSg+1TIEsYNc=,iv:LjRyav0YVKtG79roC8KRS99cVVfu8IJRpAQ9w79PFa0=,tag:K2rjIn823sER+zHezFyAZw==,type:str] - wopus-ssh-nix-cache-pk: ENC[AES256_GCM,data:hAAMdGvTduLQe+e6g0BVrvDATsVuRX5LxLQA2LqFPrdeNVPNzlWt5dNY4PjDuGKKeOyIjfTP2a4R2tLhjzQzSmoUZZVCEijohIsoDLbTfXgDDSOwXiTTr2nj3Hw4+TiuMH/VRgpIzZVjJSweuDK2UmwhbJ3wtahE7iNYD0gZet9Ibnu3iHVW4NdZs0K9joVxJoAaY8ZQi95QC0NYV/8RZ3GQFm2sQK/I1XKEAZGZ9GK5TbRUxGh3HihX68xsxBv5avpXwURp4K/CXW6VCyhAiU21+kpTPxV1x6ZiUfmPqDUmqqV57HL6+z1g6bLb+XGBNU15L0xqItmGpc3ENV2MpTP79MXA8C2eXgkBr0ylnsoFjlrkff+oJbDtHUkWaRHEQvkQtD3JKPgi97PtuBt0qWlpXRsCXnKwH565pfgKu6SGZHZ+VHpAGI3fjtroLhnoCeV6tBpibHk/ADr826IicVJWAVzxTSRfiMA7o4wji7MJxLYf2p3PRixSpQ9oXCsUPykQ1a2jfDs+J0ov+p0u,iv:AXNYaZS6fGz/Jr2zNhvmKOYKj010wtwcatItB8hRs+c=,tag:DixvP6ZaqX9l8Z8KegkvUw==,type:str] - wopus-ssh-nix-cache-pub: ENC[AES256_GCM,data:5G+qIs/J8mwZxGyWkK0nts9E+iqbCe8Or4C4+HHuSr3dyJTmKxmA3a+DpxmbyQ0IKjKQgiz+uJbbRGR7ptzmJr7JvpNhaJO2/CR3MKvsoCpmgynenO2QIqsEidU1h1gqMV6OEDI3pDY3OE6K2M8D2jdYLqMXo5RRa7emEQhXhdQZ98OFgVrLFtrB72Fi/rTJE/tP,iv:JAopM5dwItYl68GDAQublg+C1S0Md3S3G/7GJ11azxQ=,tag:WAqEju2azXgerpIBrk+krw==,type:str] -nebula-wopus-vpn: - ca-crt: ENC[AES256_GCM,data:hV4V9wqOVUhkx6EtNOz1Dd+JzOuWFwwVwFAqkZIOdF4zIAOUvJHN2iUq1bMVLJOWpMcaxTTuXKXTKPbujs8K8TDzpRQzM22SD5o8aZAyPfif/GDUFFaLBygZropM7lUD9WDbjOucCRBKoj9cbazLsabixF1gVR/lZxyPBaquoIlBWvUiFbF5P3CLQGZ5ENprHvHRuFPciiw0JqJJNme/gaz2CBXRbEYxjVFCjwFEYQrxcMxhRw+p/eHCVzUmnOBo+09HFYpBZvIY5Q8F+MPxstWIaeEzn3Spfiw9lRGw7/r6V+Vd8ppKcKWQfgVYynY=,iv:CQjMsZc4oFP4ZDifvynVrh0w1zvXX+g93HOOsdEV2WE=,tag:gRSKJbgkzyLJyHhRqVBL9A==,type:str] - stonehenge-crt: ENC[AES256_GCM,data:y1FQvKI3AOvp8K04qghseuhvaL/yYfjl1lTX2z0f1u61VfLMOPj7R0jR48D5bHXfrTD6exxny6wEy3wuWP105rkLD8oxehzNuT2jgUu85OB3w3yZHdPmW+8lftZcd21BwO0uPTab8EOB19wOCMYuGnO7JL/IRwPTFXVOmKx99+jD5mh5370yB05VVMflSlmA4iCbCvvhTmB1eHFc9a5g687Rwi5PlPEhaaEUDnjyZByO7Uu1nrBBtd5koQIDshIhuQKsVeB4AIOF6EER8dYlLSu9G6GS1cVKuaNoMiUfXLn0Y9kdDDRqetuCteGEd8euwUWGq5XVFIhlOfU6cZOR/wUskrUYWQ+3MApk6TJQQd9HBSU9SoARJZXPXX/RgCIFczeW/dIc1oPRfagnKECS4g==,iv:HSIcmYJib6SsuTbDV4zFePBryCIy0nzV8O5NSAjwuQs=,tag:bonhzMDsyvC/Gn5HLHrJkQ==,type:str] - stonehenge-key: ENC[AES256_GCM,data:HstlV1VXX6edP5XrPUanUfO8yK20imHXwYsV/q/W4IyA+yEH9inYt4oiw3cIvGawx7gfvOpsqU4IUxLsNr4EE83qg3YqkMrnGjYuHTe1LfGsktGhibbCqw4+kcqb12bywuXmPLb9EI4KBCzUi7EQTh4sLEGsqiujS0aUC4qutQ==,iv:RKT2ZM1NeA4MmfbyVvIQ96lNvErSydF8668oHyo4LHg=,tag:EhZlHF7PdAQ0whu/JxIbWw==,type:str] -gitlab-marge-bot: - token: ENC[AES256_GCM,data:sdTkMT1o6XfyICqf7KWGs0s7YP8o8GEYrF4=,iv:uYbuKsJ9YpdzWC7c1awGf8a43bleD6vpl84UO+CMhzA=,tag:7eOhPtOAQnKAFZjwWV5AIg==,type:str] - ssh-secret-key: ENC[AES256_GCM,data:SFM+IRAl1OFJU2QLVHcaBrX6qmrprRNT45aQYnTauiRW1EQ0hZrR9VhDZi9DNNdbTE1pPiGQnT7sKdYbegbBkvF6YtvQIO/3BIGUmmHMACw3MfjlQmOqHr5pjxdQQSneHCN4448tvY9Gd2Q04pZC8tvGoYw5DSmHVXnb6YN9UsX5uCTobxg54RkHByTAGF2++r27RLD/96crVqWBQwRltnpz2hK47Jtp+Dy9weALA+gBppTsKb4e9uaEYaZ9M2fRZqT6qFggDf+orSBI1uBqrglYk7iM0NOsB3Tvk8P5emOT7LxyPce5ZEhvnTOJEnC+YyPeMPLGeICdu+1YTmsOB9ORwKm5IFjTZ6SlBUofTN8c+GJkiReaxcgyf5nSQKK+EgbdrKch+c0okZo4zNiWcDk5MAuNF3tGl4lS/coOwMoVX2jMfUmfQqeidR3spTb9lMlbBe5EXLtgWgRUvQQT/oEhwuLjisUrbTTmjHufwUNDI2yVmCFS0r0u6DadYwWIS/p5wTbQBMzxaznBoA0e,iv:C7pa3z4d69juElVV6vwxQ+hxkUFIv/0efpOx4bgN1I0=,tag:A8J69d5i1qN2tEdn7tikuQ==,type:str] -renovate-bot: - token: ENC[AES256_GCM,data:oVWQKzIFTxtRbCs1Lv/Xewg9TkWTkYqwOFq7PTtaRSAm52GMbM/VFdRdLTvtACamoaDp,iv:loVzsYyzv4GI9IB/n1v/OOYenq1TXjuC1TNZfyu1nRk=,tag:LZGeAWIEkcy+JV97vIA0wg==,type:str] - env: ENC[AES256_GCM,data:5ELu20a39gFQQ+tKS5Jtzy7zxtKsrZ6QbvbSeSdSPUx+XYHOCpOe8CuswI6A+2wKP8D8X41gebOKXsFdoxsLrcpoVg==,iv:ReemN9DtvDR5Ue9quO52oJrFME8x7GxILbf9H5t7dmg=,tag:3T+RTz1PIQRcAkrnl9gAOQ==,type:str] -sops: - age: - - recipient: age1zrgu7w8059xydagm60phnffghvfe9h2ca58cx8qwagqpyfuvs9fqw79c8h - enc: | - -----BEGIN AGE ENCRYPTED FILE----- - YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBXWXZkSWUwZ0t0ekhBckxS - M0lIQ1FpWkY2dXhTVmZuYjJxeXhQSW85Ulg0Cm9GV1BqS29wU2FkaEVzazcwbCs1 - Zy9tV0ZxcFBwbFFaMzRwUWFHWUZadDAKLS0tIFdoMkVkZitjNmJhTUVMUjBQdjdi - TjFMZnZDelY2NWtwd1dETFUrUE44eGsKdRVF1QWlhO3obls8Fm+PSs/yzJOUbQ80 - GoWMqeD8qPVhO99Cy9DT0GWOk3DJQNQ55I7w6ctrhJ3XuZHzTyAqlg== - -----END AGE ENCRYPTED FILE----- - - recipient: age13y65zemwlfnf5pszspeh87utv5jrfm35varxjdsh78xhfhs7la3scm9l9g - enc: | - -----BEGIN AGE ENCRYPTED FILE----- - YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB5TEIyU1dtK0ZWQmJLY0Z0 - cThwbTdmUkF5ZGgraFBSMjZRUmpiSkxZUFdJCnhBTDd2THlmczk1SE1qZ2VnRk5a - ejZGY2U1L1IxZ3BrdURNTURwRUJCaWsKLS0tIFdEUW5Kcmw5eGE5cFJYejRXTDYr - dC9MaDUvcG96djVFU1Fpb1NKZThNaUEKkxPikf5+veTmrXHU4sxtJO/LsQ3YB4j+ - vkIWWw4qV8zRrh+XxFXrFUURhDp11m/nlpzPERxjNzRs13VS2tXTrw== - -----END AGE ENCRYPTED FILE----- - lastmodified: "2026-02-13T22:27:32Z" - mac: ENC[AES256_GCM,data:5HPABMfLg3ZGpuAH22u7PK+aeLhCfG+hhQwCVvuInk5KLabNne0tDp11I4CscPOx2235uqWUVH21vor8pcITEIFv9ZkJTvrT4Jmg1elXR1Pwv++2Eq61XgJC9LkQe42s59WXtKSg0jNfbT2HjUXdYEhnc6efTNiNivv4ekkPHJA=,iv:zf6PpnQ7L/bQfD1OHoXgsB0HCHyVxu+pbNmBBbZuh7k=,tag:JYH8OVI/SbZcufl4gQP3dg==,type:str] - pgp: - - created_at: "2026-01-01T21:36:47Z" - enc: |- - -----BEGIN PGP MESSAGE----- - - hQEMAzy6JxafzLr5AQf+I8mDQ00zcPxP4GJh5ldaVJSZ95OF7Pk0TmNmtQLaBHqE - Gj8MPa3CE8MyZBtFrWjt52yKcg0wIznd1Uo0HGteW2cMxGezCqioTIqNgXSQ+h/V - T751kH0MBOVscJUoEx3D7sdCsvk70WwnN2FdkFpA1NIDqsoHCT4MXGzcAMVTv/+K - Y630VFguV0Fcmy16Kry1EFVDSorio6BxwBnK2PG/uAQOEjTA8fLTVutc+h7glqjU - iiNPsv6MtB5gTp/Q+IPHgGmPpyCP2vN7i0ArVNFRQ2tf9tIeo/5FfgmWCH8CTcr5 - deK/UPwJ3u2o4OsVLQryx9TBVnBcFG31f+/kwIG4CNJcAZxl1w0DbS+zHtIu1Bo5 - oRAxj00EeM8Vp7FFA70Z38HSzFyvawomSrtzRNhRPoLOPemG59WH4621BL1HC9Rz - 8lhSEVRdw/BjmtNRRcLsw9NrAjGsHkkhkEluY1U= - =bhCO - -----END PGP MESSAGE----- - fp: 0FECE8316E74BA6F44EFC21A2F8F21CE8721456B - unencrypted_suffix: _unencrypted - version: 3.11.0 diff --git a/switch-stonehenge b/switch-stonehenge deleted file mode 100755 index 066d2a7..0000000 --- a/switch-stonehenge +++ /dev/null @@ -1 +0,0 @@ -nixos-rebuild switch --flake .#stonehenge -L --target-host stonehenge-lan --build-host stonehenge-lan --sudo diff --git a/system/marge-bot/default.nix b/system/marge-bot/default.nix deleted file mode 100644 index 4a5f1d6..0000000 --- a/system/marge-bot/default.nix +++ /dev/null @@ -1,374 +0,0 @@ -# TODO: -# - [ ] Check that types.oneOf is the correct way to define options with multiple values - -{ - config, - lib, - pkgs, - ... -}: -let - cfg = config.services.marge-bot; - inherit (lib) - mkOption - mkEnableOption - types - ; - - margeBotConfig = cfg.settings // { - gitlab-url = cfg.gitlabUrl; - auth-token-file = cfg.authTokenFile; - ssh-key-file = cfg.sshKeyFile; - }; - - margeBotConfigFile = pkgs.writeText "marge-bot-config.yaml" ( - pkgs.lib.generators.toYAML { } margeBotConfig - ); -in -{ - options = { - services.marge-bot = { - enable = mkEnableOption "GitLab Marge Bot service"; - - package = mkOption { - type = types.package; - default = pkgs.marge-bot; - defaultText = "pkgs.marge-bot"; - description = "Marge Bot package to use"; - }; - - gitlabUrl = mkOption { - type = types.str; - default = "https://gitlab.com"; - description = "GitLab instance URL"; - example = "https://gitlab.example.com"; - }; - - authTokenFile = mkOption { - type = types.str; - description = "Path to file containing GitLab authentication token"; - }; - - sshKeyFile = mkOption { - type = types.str; - description = "Path to SSH private key file for Git operations"; - }; - - user = mkOption { - type = types.str; - default = "gitlab-marge-bot"; - description = "User account under which marge-bot runs"; - }; - - group = mkOption { - type = types.str; - default = "gitlab-marge-bot"; - description = "Group account under which marge-bot runs"; - }; - - homeDirectory = mkOption { - type = types.str; - default = "/var/lib/marge-bot"; - description = "Home directory for the marge-bot user"; - }; - - settings = mkOption { - default = { }; - type = types.submodule { - freeformType = - with types; - attrsOf (oneOf [ - str - bool - int - float - (listOf str) - ]); - - options = { - # Based on https://marge-bot.readthedocs.io/en/latest/configuration - - use-https = mkOption { - type = types.bool; - default = false; - description = "Use HTTP(S) instead of SSH for GIT repository access"; - }; - - embargo = mkOption { - type = types.nullOr types.str; - default = null; - example = "Friday 1pm - Monday 9am"; - description = '' - Time(s) during which no merging is to take place. - Example: "Friday 1pm - Monday 9am" - ''; - }; - - use-merge-strategy = mkOption { - type = types.bool; - default = false; - description = '' - Use git merge instead of git rebase to update the source branch (EXPERIMENTAL). - If you need to use a strict no-rebase workflow. - ''; - }; - - rebase-remotely = mkOption { - type = types.bool; - default = false; - description = '' - Instead of rebasing in a local clone of the repository, use GitLab's - built-in rebase functionality, via their API. Note that Marge can't add - information in the commits in this case. - ''; - }; - - add-tested = mkOption { - type = types.bool; - default = false; - description = '' - Add "Tested: marge-bot <$MR_URL>" for the final commit on branch after it passed CI. - ''; - }; - - batch = mkOption { - type = types.bool; - default = false; - description = "Enable processing MRs in batches"; - }; - - add-part-of = mkOption { - type = types.bool; - default = false; - description = '' - Add "Part-of: <$MR_URL>" to each commit in MR. - ''; - }; - - batch-branch-name = mkOption { - type = types.str; - default = "marge_bot_batch_merge_job"; - description = "Branch name when batching is enabled"; - }; - - add-reviewers = mkOption { - type = types.bool; - default = false; - description = '' - Add "Reviewed-by: $approver" for each approver of MR to each commit in MR. - ''; - }; - - keep-committers = mkOption { - type = types.bool; - default = false; - description = "Keep the original commit info during rebases"; - }; - - keep-reviewers = mkOption { - type = types.bool; - default = false; - description = '' - Ensure previous "Reviewed-by: $approver" aren't dropped by --add-reviewers - ''; - }; - - impersonate-approvers = mkOption { - type = types.bool; - default = false; - description = "Marge-bot pushes effectively don't change approval status"; - }; - - merge-order = mkOption { - type = types.enum [ - "created_at" - "updated_at" - "assigned_at" - ]; - default = "created_at"; - description = '' - Order marge merges assigned requests. - Options: created_at (default), updated_at or assigned_at. - ''; - }; - - approval-reset-timeout = mkOption { - type = types.str; - default = "0s"; - description = '' - How long to wait for approvals to reset after pushing. - Only useful with the "new commits remove all approvals" option in a project's settings. - This is to handle the potential race condition where approvals don't reset in GitLab - after a force push due to slow processing of the event. - ''; - }; - - project-regexp = mkOption { - type = types.str; - default = ".*"; - example = "some_group/.*"; - description = '' - Only process projects that match; e.g. 'some_group/.*' or '(?!exclude/me)'. - ''; - }; - - ci-timeout = mkOption { - type = types.str; - default = "15min"; - description = "How long to wait for CI to pass"; - }; - - git-timeout = mkOption { - type = types.str; - default = "120s"; - description = "How long a single git operation can take"; - }; - - git-reference-repo = mkOption { - type = types.nullOr types.str; - default = null; - description = "A reference repo to be used when git cloning"; - }; - - branch-regexp = mkOption { - type = types.str; - default = ".*"; - description = '' - Only process MRs whose target branches match the given regular expression. - ''; - }; - - source-branch-regexp = mkOption { - type = types.str; - default = ".*"; - description = '' - Only process MRs whose source branches match the given regular expression. - ''; - }; - - debug = mkOption { - type = types.bool; - default = false; - description = "Debug logging (includes all HTTP requests etc)"; - }; - - run-manual-jobs = mkOption { - type = types.bool; - default = false; - description = "Add this flag to have Marge run on manual jobs within the pipeline"; - }; - - use-no-ff-batches = mkOption { - type = types.bool; - default = false; - description = "Disable fast forwarding when merging MR batches"; - }; - - use-merge-commit-batches = mkOption { - type = types.bool; - default = false; - description = '' - Use merge commit when creating batches, so that the commits in the batch MR - will be the same with in individual MRs. Requires sudo scope in the access token. - ''; - }; - - skip-ci-batches = mkOption { - type = types.bool; - default = false; - description = "Skip CI when updating individual MRs when using batches"; - }; - - cli = mkOption { - type = types.bool; - default = false; - description = "Run marge-bot as a single CLI command, not a service"; - }; - - guarantee-final-pipeline = mkOption { - type = types.bool; - default = false; - description = "Guaranteed final pipeline when assigned to marge-bot"; - }; - - exc-comment = mkOption { - type = types.nullOr types.str; - default = null; - description = '' - Provide additional text, like a log URL, to append to some exception-related MR comments. - ''; - }; - - custom-approver = mkOption { - type = types.listOf types.str; - default = [ ]; - description = '' - Specify one or more approver usernames to accept instead of asking GitLab. - For CE approval use. - ''; - }; - - custom-approvals-required = mkOption { - type = types.int; - default = 0; - description = '' - Required number of approvals from --custom-approval. - For CE approval use. - ''; - }; - - hooks-directory = mkOption { - type = types.nullOr types.str; - default = null; - description = "Path to the directory where your custom hooks are located"; - }; - }; - }; - description = '' - Settings for the marge-bot configuration. - See https://marge-bot.readthedocs.io/en/latest/configuration - for detailed information about all available settings. - ''; - }; - }; - }; - - config = lib.mkIf cfg.enable { - - warnings = - (lib.optional (lib.isStorePath cfg.authTokenFile) '' - services.marge-bot.authTokenFile points to a file in the Nix store. - You should use a quoted absolute path instead. - '') - ++ (lib.optional (lib.isStorePath cfg.sshKeyFile) '' - services.marge-bot.sshKeyFile points to a file in the Nix store. - You should use a quoted absolute path instead. - ''); - - users.users.${cfg.user} = { - isSystemUser = true; - group = cfg.group; - home = cfg.homeDirectory; - createHome = true; - }; - - users.groups.${cfg.group} = { }; - - systemd.services.gitlab-marge-bot = { - description = "GitLab Marge Bot Service"; - wantedBy = [ "multi-user.target" ]; - after = [ "network.target" ]; - - serviceConfig = { - Type = "simple"; - User = cfg.user; - Group = cfg.group; - WorkingDirectory = cfg.homeDirectory; - ExecStart = "${lib.getExe cfg.package} --config-file ${margeBotConfigFile}"; - Restart = "on-failure"; - RestartSec = "10s"; - }; - }; - - }; -} diff --git a/system/renovate-bot/default.nix b/system/renovate-bot/default.nix deleted file mode 100644 index db954f7..0000000 --- a/system/renovate-bot/default.nix +++ /dev/null @@ -1,288 +0,0 @@ -{ - config, - lib, - pkgs, - ... -}: -let - cfg = config.services.renovate-bot; - inherit (lib) - mkOption - mkEnableOption - types - ; - - renovateConfig = cfg.settings // { - platform = cfg.platform; - endpoint = cfg.endpoint; - }; - - renovateConfigFile = pkgs.writeText "renovate-config.json" (builtins.toJSON renovateConfig); - - renovateWrapper = pkgs.writeShellScript "renovate-wrapper" '' - export RENOVATE_TOKEN=$(cat "${cfg.tokenFile}") - exec ${lib.getExe cfg.package} ${lib.concatStringsSep " " cfg.repositories} - ''; -in -{ - options = { - services.renovate-bot = { - enable = mkEnableOption "Renovate Bot service"; - - package = mkOption { - type = types.package; - default = pkgs.renovate; - defaultText = "pkgs.renovate"; - description = "Renovate Bot package to use"; - }; - - platform = mkOption { - type = types.enum [ - "gitlab" - "github" - "bitbucket" - "azure" - ]; - default = "gitlab"; - description = "Git platform to use"; - }; - - endpoint = mkOption { - type = types.str; - default = "https://gitlab.com"; - description = "Git platform endpoint URL"; - example = "https://gitlab.example.com"; - }; - - tokenFile = mkOption { - type = types.str; - description = "Path to file containing authentication token"; - }; - - envFile = mkOption { - type = types.nullOr types.str; - default = null; - description = "Path to file containing environment variables (in .env format)"; - }; - - user = mkOption { - type = types.str; - default = "renovate-bot"; - description = "User account under which renovate-bot runs"; - }; - - group = mkOption { - type = types.str; - default = "renovate-bot"; - description = "Group account under which renovate-bot runs"; - }; - - homeDirectory = mkOption { - type = types.str; - default = "/var/lib/renovate-bot"; - description = "Home directory for the renovate-bot user"; - }; - - repositories = mkOption { - type = types.listOf types.str; - default = [ ]; - description = "List of repositories to monitor (format: owner/repo)"; - example = [ - "myorg/myrepo" - "myorg/another-repo" - ]; - }; - - schedule = mkOption { - type = types.nullOr types.str; - default = null; - example = "before 6am"; - description = "Schedule for Renovate runs (systemd timer OnCalendar format)"; - }; - - logLevel = mkOption { - type = types.enum [ - "fatal" - "error" - "warn" - "info" - "debug" - "trace" - ]; - default = "info"; - description = "Log level for Renovate (set via LOG_LEVEL environment variable)"; - }; - - extraPackages = mkOption { - type = types.listOf types.package; - default = [ ]; - description = "Extra packages to add to PATH for the Renovate Bot service"; - example = "[ pkgs.curl pkgs.jq ]"; - }; - - nodeMemoryLimit = mkOption { - type = types.int; - default = 4096; - description = "Node.js memory limit in MB (--max-old-space-size)"; - }; - - settings = mkOption { - default = { }; - type = types.submodule { - freeformType = - with types; - attrsOf (oneOf [ - str - bool - int - float - (listOf str) - (attrsOf anything) - ]); - - options = { - # Common Renovate configuration options - # Based on https://docs.renovatebot.com/configuration-options/ - - requireConfig = mkOption { - type = types.bool; - default = false; - description = "Require renovate.json config file"; - }; - - dryRun = mkOption { - type = types.bool; - default = false; - description = "Run in dry-run mode (no PRs created)"; - }; - - printConfig = mkOption { - type = types.bool; - default = false; - description = "Print the resolved config and exit"; - }; - - gitAuthor = mkOption { - type = types.nullOr types.str; - default = null; - example = "Renovate Bot "; - description = "Git author for commits"; - }; - - gitPrivateKey = mkOption { - type = types.nullOr types.str; - default = null; - description = "Private key for git operations"; - }; - - includeForks = mkOption { - type = types.bool; - default = false; - description = "Include forked repositories"; - }; - - includeMirrors = mkOption { - type = types.bool; - default = false; - description = "Include mirrored repositories"; - }; - - autodiscover = mkOption { - type = types.bool; - default = false; - description = "Auto-discover repositories"; - }; - - autodiscoverFilter = mkOption { - type = types.nullOr types.str; - default = null; - description = "Filter for auto-discovered repositories"; - }; - - timezone = mkOption { - type = types.nullOr types.str; - default = null; - example = "America/Sao_Paulo"; - description = "Timezone for scheduling"; - }; - - prConcurrentLimit = mkOption { - type = types.int; - default = 10; - description = "Maximum number of concurrent PRs"; - }; - - prHourlyLimit = mkOption { - type = types.int; - default = 2; - description = "Maximum number of PRs per hour (0 = unlimited)"; - }; - - branchConcurrentLimit = mkOption { - type = types.int; - default = 10; - description = "Maximum number of concurrent branches (0 = unlimited)"; - }; - }; - }; - description = '' - Settings for the Renovate configuration. - See https://docs.renovatebot.com/configuration-options/ - for detailed information about all available settings. - ''; - }; - }; - }; - - config = lib.mkIf cfg.enable { - - warnings = ( - lib.optional (lib.isStorePath cfg.tokenFile) '' - services.renovate-bot.tokenFile points to a file in the Nix store. - You should use a quoted absolute path instead. - '' - ); - - users.users.${cfg.user} = { - isSystemUser = true; - group = cfg.group; - home = cfg.homeDirectory; - createHome = true; - }; - - users.groups.${cfg.group} = { }; - - systemd.services.renovate-bot = { - description = "Renovate Bot Service"; - after = [ "network.target" ]; - - path = [ pkgs.git ] ++ cfg.extraPackages; - - environment = { - RENOVATE_CONFIG_FILE = renovateConfigFile; - LOG_LEVEL = cfg.logLevel; - NODE_OPTIONS = "--max-old-space-size=${toString cfg.nodeMemoryLimit}"; - }; - - serviceConfig = { - Type = "oneshot"; - User = cfg.user; - Group = cfg.group; - WorkingDirectory = cfg.homeDirectory; - ExecStart = "${renovateWrapper}"; - } - // lib.optionalAttrs (cfg.envFile != null) { - EnvironmentFile = cfg.envFile; - }; - }; - - systemd.timers.renovate-bot = lib.mkIf (cfg.schedule != null) { - description = "Renovate Bot Timer"; - wantedBy = [ "timers.target" ]; - timerConfig = { - OnCalendar = cfg.schedule; - Persistent = true; - }; - }; - }; -}