Code Monkey home page Code Monkey logo

nix-config's Introduction

built with nix hydra status

My NixOS configurations

Here's my NixOS/home-manager config files. Requires Nix flakes.

Looking for something simpler to start out with flakes? Try my starter config repo.

Highlights:

  • Multiple NixOS configurations, including desktop, laptop, server
  • Opt-in persistence through impermanence + blank snapshotting
  • Encrypted single BTRFS partition
  • Fully declarative self-hosted stuff
  • Deployment secrets using sops-nix
  • Mesh networked hosts with tailscale and headscale
  • Flexible Home Manager Configs through feature flags
  • Extensively configured wayland environments (sway and hyprland) and editor (neovim)
  • Declarative themes and wallpapers with nix-colors
  • Hydra CI/CD server and binary cache that uses the desktops as remote builders

Structure

  • flake.nix: Entrypoint for hosts and home configurations. Also exposes a devshell for boostrapping (nix develop or nix-shell).
  • lib: A few lib functions for making my flake cleaner
  • hosts: NixOS Configurations, accessible via nixos-rebuild --flake.
    • common: Shared configurations consumed by the machine-specific ones.
      • global: Configurations that are globally applied to all my machines.
      • optional: Opt-in configurations my machines can use.
    • atlas: Desktop PC - 32GB RAM, R5 3600x, RX 5700XT | Hyprland
    • pleione: Lenovo Ideapad 3 - 8GB RAM, R7 5700u | Hyprland
    • maia: Secondary Desktop PC - 16GB RAM, i5 6600, GTX 970 | Server
    • merope: Raspberry Pi 4 - 8GB RAM | Server
    • celaeno: Oracle Could VPS (Ampere) - 24GB RAM & 4vCPUs | Server
    • alcyone: Vultr VPS - 1GB RAM & 1 vCPU | Server
  • home: My Home-manager configuration, acessible via home-manager --flake
    • Each directory here is a "feature" each hm configuration can toggle, thus customizing my setup for each machine (be it a server, desktop, laptop, anything really).
  • modules: A few actual modules (with options) I haven't upstreamed yet.
  • overlay: Patches and version overrides for some packages. Accessible via nix build.
  • pkgs: My custom packages. Also accessible via nix build. You can compose these into your own configuration by using my flake's overlay, or consume them through NUR.
  • templates: A couple project templates for different languages. Accessible via nix init.

About the installation

All my computers use a single btrfs (encrypted on all except headless systems) partition, with subvolumes for /nix, a /persist directory (which I opt in using impermanence), swap file, and a root subvolume (cleared on every boot).

Home-manager is used in a standalone way, and because of opt-in persistence is activated on every boot with loginShellInit.

How to bootstrap

All you need is nix (any version). Run:

nix-shell

If you already have nix 2.4+, git, and have already enabled flakes and nix-command, you can also use the non-legacy command:

nix develop

nixos-rebuild --flake . To build system configurations

home-manager --flake . To build user configurations

nix build (or shell or run) To build and use packages

sops To manage secrets

Secrets

For deployment secrets (such as user passwords and server service secrets), I'm using the awesome sops-nix. All secrets are encrypted with my personal PGP key (stored on a YubiKey), as well as the relevant systems's SSH host keys.

On my desktop and laptop, I use pass for managing passwords, which are encrypted using (you bet) my PGP key. This same key is also used for mail signing, as well as for SSH'ing around.

Tooling and applications I use

Most relevant user apps daily drivers:

  • hyprland + swayidle + swaylock
  • waybar
  • neovim
  • fish + starship
  • kitty
  • qutebrowser
  • neomutt + mbsync
  • khal + khard + todoman + vdirsyncer
  • gpg + pass
  • tailscale
  • podman
  • zathura
  • wofi
  • bat + fd + rg
  • kdeconnect
  • sublime-music

Some of the services I host:

  • hydra
  • navidrome
  • deluge
  • prometheus
  • websites (such as https://m7.rs)
  • minecraft
  • headscale

Nixy stuff:

  • nix-colors
  • sops-nix
  • impermanence
  • home-manager
  • deploy-rs
  • and NixOS and nix itself, of course :)

Let me know if you have any questions about them :)

Unixpornish stuff

fakebusy clean

That's how my hyprland desktop setup look like (as of 2022 July).

nix-config's People

Contributors

badele avatar misterio77 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

nix-config's Issues

Home Manager environment not activating under an impermanence setup

I've been studying your impermanence setup (really cool stuff!) while trying things out on a VM, and it's been working well so far for the system part. But I'm having trouble getting home-manager to play well with impermanence. For some reason it's just not activating on startup.

So far my setup is pretty simple (mostly copied from yours):

persistence = {
  "/persist/home/${config.home.username}" = {
    directories = [
      "Documents"
      "Downloads"
      "Pictures"
      "Videos"
    ];
    allowOther = true;
  };
};

But after a reboot my home manager environment is not activated. I need to do the following every time:

nix shell home-manager
mkdir -p ~/.local/state/nix/profiles # required or home-manager will throw an error. why?
home-manager switch --flake /etc/nixos

I mean, I guess it kinda makes sense, since the home directory is cleared every time. But I'm not sure how you're handling this in your config. Tbh, I'm still a bit fuzzy with how home-manager manages the user environments, so I'm not really sure how to about debugging this. Any tips?

Reasons for choosing btrfs/luks rather than zfs

First I want to thank you for putting together some very useful configs. Between your starter config and this one, it's a really nice on-ramp for more interesting configurations. There are so many ways to to put together a nixos config, and I appreciate the choices you've made here in terms of structure, and simplicity vs capability. The contrast of your starter to this one is particularly useful since it gives some insight into how to ramp up complexity usefully as necessary.

Anyway, I'm doing a rebuild I've already put off for too long, and was considering the erase-your-darlings approach. I was going to use zfs since that's what's usually called for in the various recipes, but using native encryption to avoid the performance hit. I know it leaks some meta, but it feels like an ok trade-off.

Can I ask what made you decide to go with btrfs instead of zfs for that, and if there've been any downsides?

Advice for encrypt secrets that can't be encrypted with sops-nix because they are needed during runtime

Hey, I'm using your config as a template for my own but I have some submodules that have some secrets I want to encrypt within the file (and they must be available at runtime because it's being ran with Home Manager). I came across this utility, scalpel but I have no clue how to incorporate it within the flake (I'm still learning about NixOS but I haven't heard of anyone using this utility before). Do you have any advice for how I can modify the flake file to incorporate the utility? Thanks in advance!

flake.nix

{
  description = "My NixOS Multi-config";

  inputs = {
    # Nixpkgs
    nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";

    # Home manager
    home-manager = {
      url = "github:nix-community/home-manager";
      inputs.nixpkgs.follows = "nixpkgs";
    };

    sops-nix = {
      url = "github:Mic92/sops-nix";
      inputs.nixpkgs.follows = "nixpkgs";
      inputs.nixpkgs-stable.follows = "";
    };

    hardware.url = "github:nixos/nixos-hardware";

    # Shameless plug: looking for a way to nixify your themes and make
    # ieverything match nicely? Try nix-colors!
    nix-colors.url = "github:misterio77/nix-colors";

    firefox-addons = {
      url = "gitlab:rycee/nur-expressions?dir=pkgs/firefox-addons";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };

  outputs = { self, nixpkgs, home-manager, ... }@inputs:
    let
      inherit (self) outputs;
      lib = nixpkgs.lib // home-manager.lib;
      systems = [ "x86_64-linux" ];
      forEachSystem = f: lib.genAttrs systems (sys: f pkgsFor.${sys});
      pkgsFor = nixpkgs.legacyPackages;
      dotfilesLib = rec {
        runtimeRoot = "/home/novaviper/Desktop/nix-config";
        runtimePath = path:
          let
            # This is the `self` that gets passed to a flake `outputs`.
            rootStr = toString self;
            pathStr = toString path;
          in assert lib.assertMsg (lib.hasPrefix rootStr pathStr)
            "${pathStr} does not start with ${rootStr}";
          runtimeRoot + lib.removePrefix rootStr pathStr;
      };
    in {
      inherit lib;
      # Your custom packages
      # Acessible through 'nix build', 'nix shell', etc
      packages = forEachSystem (pkgs: import ./pkgs { inherit pkgs; });

      # Devshell for bootstrapping
      # Acessible through 'nix develop' or 'nix-shell' (legacy)
      devShells = forEachSystem (pkgs: import ./shell.nix { inherit pkgs; });

      # Your custom packages and modifications, exported as overlays
      overlays = import ./overlays { inherit inputs outputs; };
      # Reusable nixos modules you might want to export
      # These are usually stuff you would upstream into nixpkgs
      nixosModules = import ./modules/nixos;
      # Reusable home-manager modules you might want to export
      # These are usually stuff you would upstream into home-manager
      homeManagerModules = import ./modules/home-manager;

      # NixOS configuration entrypoint
      # Available through 'nixos-rebuild --flake .#your-hostname'
      nixosConfigurations = {
        ryzennova = lib.nixosSystem {
          modules = [ ./hosts/ryzennova ];
          specialArgs = { inherit inputs outputs dotfilesLib; };
        };
        /* thinknova = lib.nixosSystem {
             modules = [ ./hosts/thinknova ];
             specialArgs = { inherit inputs outputs; };
           };
        */
      };

      # Standalone home-manager configuration entrypoint
      # Available through 'home-manager --flake .#your-username@your-hostname'
      homeConfigurations = {
        "novaviper@ryzennova" = lib.homeManagerConfiguration {
          modules = [ ./home/novaviper/ryzennova.nix ];
          pkgs = pkgsFor.x86_64-linux; # Home-manager requires 'pkgs' instance
          extraSpecialArgs = { inherit inputs outputs dotfilesLib; };
        };
        /* "novaviper@thinknova" = lib.homeManagerConfiguration {
             modules = [ ./home-manager/home.nix ];
             pkgs = pkgsFor.x86_64-linux; # Home-manager requires 'pkgs' instance
             extraSpecialArgs = { inherit inputs outputs; };
           };
        */
      };
    };
}

QUESTION: How did you fix the problem of wofi on waybar with systemd integration setup?

I recently switched my waybar setup with systemd integration, following the a1cbf69 commit. However, I've encountered an issue where wofi doesn't seem to work correctly in this setup. Unlike when I execute the wofi command in the terminal, wofi on waybar only detects significantly fewer applications. Furthermore, when I click the custom/menu icon on waybar to open drun menu, drun menu pops up no matter where I click on waybar. I'm curious about how you resolved these issues. I've been following your commits but haven't been able to figure it out myself. I would appreciate your help. Thanks.

How are things connected to eachother?

Hi! I am new to the self hosting stuff, and your config is extremly inspiring! I've spent a lot of time trying to understand your setup, but a lot of it is still very confusing.

I am trying to understand the basics of how you handle networking between devices. I'll simplify my question to only involve 3 kind of devices:

  1. A server running actual services
  2. A cheap vps server running headscale and whatnot
  3. A personal device (laptop/phone/etc)

I cannot open ports on the router for my kind (1) device, so if I understand things correctly, I need to point the dns records to (2), and connect (1) and (3) to the headscale network created by (2). I could then use nginx running on (2) to point requests going to a public url to a magicdns url pointing to a server running on (1), effectively managing to expose the service running on (1) to the outside world.

I know very little about the topic, so idk if what I'm saying makes any sense.

A few questions:

  • What if I wanted to host a game server (factorio/terraria/satisfactory/etc) on (1). From what I read, those kind of servers don't use http, so something like nginx is useless here, right?
  • Assume I own example.com . Can I make it so I can type out service.example.com in my browser to access a service running on (1) which is not accessible from people outside the headspace network? I know I could simply use magicdns, but that would require typing out the port number each time, right?
  • Can I host a mailserver on (1) which also gets tunneled through (2)?

I hope asking this here is ok, and thanks in advance!

Firefox add-ons issue

Hi,
First of all thanks a lot for your work, it was the most helpful resource in my NixOS journey.
I'm trying to add Firefox add-ons to my setup through hm. I use your Standard config. Inspired by this repo, I added

firefox-addons = {
  url = "gitlab:rycee/nur-expressions?dir=pkgs/firefox-addons";
  inputs.nixpkgs.follows = "nixpkgs";
};

to the inputs of my flake.nix and

extensions = with pkgs.inputs.firefox-addons; [
  ublock-origin
  bitwarden
];

to my Firefox entry in home.nix.
When I run the hm switch command I get this error:

error: attribute 'inputs' missing

Is there something wrong I'm doing?
Thanks!

QUESTION about nix-shell getting error: input 'hyprland-plugins/systems' follows a non-existent input 'hyprland-plugins/hyprland/systems'

So I am trying to test/learn nixos with your setup and learn and make my own version on my laptop.

What I did was

nix-shell

sudo home-manager switch --flake .#misterio@pleione

output

do you want to allow configuration setting 'extra-substituters' to be set to 'https://cache.m7.rs https://nix-gaming.cachix.org' (y/N)? y
do you want to permanently mark this value as trusted (y/N)? y
do you want to allow configuration setting 'extra-trusted-public-keys' to be set to 'cache.m7.rs:kszZ/NSwE/TjhOcPPQ16IuUiuRSisdiIwhKZCxguaWg= nix-gaming.cachix.org-1:nbjlureqMbRAxR1gJ/f3hxemL9svXaZF/Ees8vCUUs4=' (y/N)? y
do you want to permanently mark this value as trusted (y/N)? y
error: input 'hyprland-plugins/systems' follows a non-existent input 'hyprland-plugins/hyprland/systems'

For some reason home-manager switch --flake .#misterio@pleione didn't work without sudo.
Could not find suitable profile directory, tried /home/penguin/.local/state/home-manager/profiles and /nix/var/nix/profiles/per-user/penguin

libsForQt5.qtstyleplugins messes up QT apps.

I have been configuring my nix config slowly based on this config (because it's really great), and I've come across a bit of an issue. Namely, using qt apps with the default settings in this project.

When using QT applications such as KDEConnect or kde-authentication-agent, I believe that there is a icon issue which causes the layout of the application to have issues.

As you can see in this image, the help and close buttons on KDEConnect have an incorrect layout. The details button on kde-authentication-agent, also has the same issue. I'm relatively sure that the icons for kde-authentication-agent are also very wrong.

image

To note: switching to adwaita / adwaita-qt works.

Does anyone else have these issues or is it just me?

I'm not sure whether this is a nix-colors issue or a materia theme issue, or a qtstyleplugins issue, but I know you (@Misterio77) are probably the best person to ask.

Do you persist `/etc/nixos`?

In ephemeral setups, you're supposed to persist /etc/nixos (or wherever your config is located) somewhere right? I looked through your config:

environment.persistence = {
"/persist" = {
directories = [
"/var/lib/systemd"
"/var/lib/nixos"
"/var/log"
"/srv"
];
};
};

But I can't find where are you persisting your config directory. Do you even persist it at all?

btrfs-optin-persistence issue

I really like your work on this config setup. Trying to implement this btrfs-optin-persistence solution in my configs, but I'm having troubles with proper setup. Does it require special btrfs/subvolumes configuration? After enabling this feature my system won't boot.

QUESTION: how to configure new Hydra project?

Hello,
first of all thanks a lot for the great repo you have created. It's very inspiring.
I'm trying to use Hydra to build my main branch, but I'm not able to create a working Hydra project in the Hydra webpage.
Could you please share which are the correct values for "Declarative spec file" (.hydra.json?) , "Declarative input type" (Git checkout?) and "Declarative input value" fields on the new Hydra project form?

Thanks
Tommaso

Some question with home-manager

I am nearly new one to nixos, after reading your config I learned a lot, and here comes some question.

home-manager.users.misterio = import home/${config.networking.hostName}.nix;

Here, you used home-manager under hosts directory(or say, in nixosConfiguration use?), I did the same way, while running nixos-rebuild switch --flake . got some error like this:

error: The option `home-manager' does not exist. Definition values:
       - In `/nix/store/g57dw7gngchrksv935nhziyl45nj0hwf-source/hosts/common/users/aimi':
           {
             users = {
               aimi = <function, args: {inputs, pkgs}>;
             };
           }

and find something in nixos wiki like this To use it inside nixosConfigurations in a Flake, put home-manager in your inputs and in your configuration modules import home-manager.nixosModules.home-manager, then you can use it as above.
It's in here . But I did'nt see any thing like home-manager.nixosModules.home-manager in your config, did you use other method or I had some misunderstanding with nixos wiki's word?

Thanks

QUESTION: impermanence and standalone home-manager

Hello,
in nix-starter-config you say that to use impermanence with home-manager we have to use home-manager as a module. But it seems that you use it standalone. What is your solution?
Thanks for all the great stuff

error: The option `home-manager.users.eyduh.fontProfiles' does not exist.

I seem to run into the error in the title when I try and build this.

https://github.com/Misterio77/nix-config/blob/8e2d6e7741b753f733fa8ca09960da9de479b806/home/misterio/features/desktop/common/font.nix#L2C14-L2C14

I had a look in the home manager docs and found a similar option fonts.fontconfig which returns the same error if I swap out fontProfiles.

Havent managed to build this at all yet so maybe the problem lies elsewhere tho

Installation guide

Hi, thanks a lot for this repo!

I wonder if you could add some lines on how you install this on a new system, such as creating the partitions/encrypted partition and so on.
I'm trying to set up encryption but can't get the system to boot

Thanks!

Question on LSP in nvim

Hello,
in your lsp module for nvim you define function that loads lsp if it finds the executable. Do you then configure the servers for separate projects or am I missing where you install them?
Thank you for great example config

Question about using btrfs-optin-persistence using systemd service

I've been testing out using Systemd as the initrd system on NixOS (which at best seems to be in an alpha state...) and I've run into some issues/questions with the btrfs impermanence script that I'm hoping to resolve.

in btrfs-optin-persistence.nix, wiping the root partition is now done through a systemd service rather using the postDeviceCommands value for the custom NixOS initrd. That all makes sense to me. What I am wondering is how the service dependencies make sense.

requires = [ "initrd-root-device.target" ];
before = [ "sysroot.mount" ];
wantedBy = [ "initrd-root-fs.target" ];

When I tried this order, I encountered an error where initrd-btrfs-root-wipe would be run before the actual decryption was done for the root partion and therefore the service would fail.

I was able to resolve this, but the next issue was that sysroot.mount would activate while initrd-btrfs-root-wipe was still running and therefore fail again. To resolve these issues I ended up with something like this:

after = [ "[email protected]" ];
before = [ "sysroot.mount" ];
wantedBy = [ "sysroot.mount" ];

PS: Thank you for having this as well as the nix-starter-config repository. They are by far the most helpful references I've encountered.

SOPS and fresh installation

Hello! I really like your repo structure, I'm constantly visiting it.

I decided to try sops-nix to manage sensible stuff, and I'm curious: how do you handle the first installation? Keys need to be deployed to allow decryption at activation time.

Redundant directory creation in ephemeral-btrfs.nix

I was looking at the ephemeral-btrfs module. I see you ensure needed directories exists:

echo "Creating needed directories"
mkdir -p "$MNTPOINT"/persist/var/{log,lib/{nixos,systemd}}

But as far I understand the impermanence module included in optin-persistence.nix should

environment.persistence = {
"/persist" = {
directories = [
"/var/lib/systemd"
"/var/lib/nixos"
"/var/log"
"/srv"
];
};
};

Does it make sense to ephemeral-btrfs.nix removing the above lines?

Ci/Cd?

I see you are using hydra. I'm yet to set it up myself, but as far as I can understand it let's you build nix stuff automatically. Does your setup have a way to automatically deploy your website and other things? (or do you manually call the deploy script whenever you feel like it?)

Invalid Hyprland configuration generated with hyprbars

Hyprbars file uses "plugin:hyprbars" syntax to add hyprbars configuration to the Hyprland configuration file:

However, valid syntax is to use two nested sections, see https://github.com/hyprwm/hyprland-plugins/tree/main/hyprbars:

plugin {
  hyprbars {
    ...
  }
}

Using plugin:hyprbars syntax makes Hyprland incorrectly parse the section which may follow after plugin:hyprbars in the configuration file (in my case xwayland), and skip the values defined there, because of this section parsing code:
https://github.com/hyprwm/Hyprland/blob/ea7569d7e0941d19f5f469a5fbb79bc0fa62b935/src/config/ConfigManager.cpp#L1550

Build issue - gtk theme

Hi,

I was attempting to boot strap a new machine with your config and on first boot in the shell "home-manager switch --flake ." is giving me a build error when it tries the generate a theme.

error hash mismatch in fixed-output derivation '/nix/store/5rjyafr6d7f9k1ayr9916fyfm086nfh6-source.drv':
	specified: sha256-0eCAfm/MWXv68bCl2vbVbvgv8DiUH09TAUhoKq7Ow0k=
	   got:    sha256-h5pxL6Z+ZEOgALxm7Hn0aQh0++/8lNURl0ooGJ+cYqg=
error: 1 dependencies of derivation '/nix/store...generated-gtk-theme-paraiso.drv' failed to build
error: 1 dependencies of derivation '/nix/store...generated-gtk-theme-paraiso-fish-completions.drv' failed to build
error: 1 dependencies of derivation '/nix/store...home-manager-path.drv failed to build
error: 1 dependencies of derivation '/nix/store...home-manager-generation.drv failed to build

Also, I don't know if you care, but if you have both colorscheme and wallpaper commented out there's an "infinite recursion encountered" error.

Thanks

Home-Manager not working as anticipated

First of all: Great work! Your starter-config helped me to understand nix-flake configuration a little more and your nix-config checked all the boxes for me: encrypted btrfs, impermanence, multiple users and host-machines ans everything in one flake-file.

Thanks to your nix-config I have a running NixOS although I am struggling with the home-manager (not for the first time). I can build and switch with home-manager switch --flake $user@$host but home-manager packages gives me nothing. I remember that I had problems with home-manager before (with a much simpler config) getting sway to work. Do you have any clues, what might be the problem? I basically edited your configuration to my needs.

Thank you in advance

Lutz

help with configuring gpg

hello,
first i have to thanks for this awesome config.
i had a on off relationship with nix and nixos and this config is the first complex config that was easy to understand.
i still have a lot of knowledge gaps and i struggle to setup gnupg with your config.
i mean i know how to generate keys but i still struggle to get it working :(
how do you setup a new machine with your existing key?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.