Compare commits
No commits in common. "9f66f0b37b1371200d74a2bf13335e76a651f18d" and "dd1d285ce0873e484b3ffb13d40217a5c081ef25" have entirely different histories.
9f66f0b37b
...
dd1d285ce0
8 changed files with 31 additions and 421 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -20,9 +20,10 @@ build/Release
|
||||||
.env.test.local
|
.env.test.local
|
||||||
.env.production.local
|
.env.production.local
|
||||||
.env.local
|
.env.local
|
||||||
|
# wrangler project
|
||||||
|
.dev.vars
|
||||||
|
.wrangler/
|
||||||
# Others
|
# Others
|
||||||
.direnv/
|
.direnv/
|
||||||
# Template rendering artifacts
|
# Template rendering artifacts
|
||||||
public/*.html
|
public/*.html
|
||||||
# Nix build result
|
|
||||||
result
|
|
||||||
|
|
12
README.md
12
README.md
|
@ -1,15 +1,3 @@
|
||||||
# Nixifying a new host
|
|
||||||
|
|
||||||
If you have a bunch of SSH keys in your SSH agent and get errors when trying to SSH into a fresh host, you may need to temporarily add the following config to your SSH config (obviously change the details for your case).
|
|
||||||
The key is the line "IdentitiesOnly yes" along with an identity file.
|
|
||||||
|
|
||||||
```
|
|
||||||
Host 188.245.194.78
|
|
||||||
User root
|
|
||||||
IdentityFile ~/.ssh/hetzner_personal_root.pub
|
|
||||||
IdentitiesOnly yes
|
|
||||||
```
|
|
||||||
|
|
||||||
# Adding yourself to the webring
|
# Adding yourself to the webring
|
||||||
|
|
||||||
(Please only follow this if you're an Epesooj insider. Random PRs will be closed for now.)
|
(Please only follow this if you're an Epesooj insider. Random PRs will be closed for now.)
|
||||||
|
|
168
code_server.nix
168
code_server.nix
|
@ -1,168 +0,0 @@
|
||||||
{ self, moduleWithSystem, ... }: {
|
|
||||||
flake.nixosModules.code-server = moduleWithSystem (
|
|
||||||
{ ... }: # Note: only explicit parameters are passed to this.
|
|
||||||
{ pkgs, modulesPath, lib, ... }: {
|
|
||||||
imports = [
|
|
||||||
self.inputs.disko.nixosModules.disko
|
|
||||||
(modulesPath + "/installer/scan/not-detected.nix")
|
|
||||||
(modulesPath + "/profiles/headless.nix")
|
|
||||||
(modulesPath + "/profiles/minimal.nix")
|
|
||||||
(modulesPath + "/profiles/qemu-guest.nix")
|
|
||||||
./code_server_disk.nix
|
|
||||||
];
|
|
||||||
|
|
||||||
system.stateVersion = "24.11";
|
|
||||||
|
|
||||||
boot.kernelParams = [ "zfs.zfs_arc_max=536870912" ];
|
|
||||||
boot.zfs.extraPools = [ "zroot" ];
|
|
||||||
boot.initrd.postMountCommands = lib.mkAfter ''
|
|
||||||
zfs rollback -r zroot/root@blank
|
|
||||||
'';
|
|
||||||
|
|
||||||
services.zfs.autoScrub.enable = true;
|
|
||||||
boot.loader.grub = {
|
|
||||||
enable = true;
|
|
||||||
# No need to set devices, disko will add all devices that have an EF02 partition to the list already.
|
|
||||||
# devices = [];
|
|
||||||
efiSupport = true;
|
|
||||||
efiInstallAsRemovable = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
fileSystems = {
|
|
||||||
"/var/lib/systemd" = {
|
|
||||||
device = "/persisted/var/lib/systemd";
|
|
||||||
options = [ "bind" ];
|
|
||||||
};
|
|
||||||
"/var/lib/forgejo" = {
|
|
||||||
device = "/persisted/var/lib/forgejo";
|
|
||||||
options = [ "bind" ];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
networking.hostId = "9f1dfd86"; # Required by ZFS.
|
|
||||||
networking.useNetworkd = true;
|
|
||||||
networking.firewall.logRefusedConnections = false;
|
|
||||||
|
|
||||||
nix.gc.automatic = true;
|
|
||||||
nix.gc.dates = "02:15";
|
|
||||||
|
|
||||||
services.cloud-init = {
|
|
||||||
enable = true;
|
|
||||||
network.enable = true;
|
|
||||||
settings = {
|
|
||||||
datasource_list = [ "Hetzner" ];
|
|
||||||
|
|
||||||
# The NixOS cloud-init settings declares the entire `system_info` with `lib.mkDefault`, so we need to copy the defaults from it here and make the changes we want to make.
|
|
||||||
system_info = {
|
|
||||||
paths = {
|
|
||||||
cloud_dir = "/persisted/var/lib/cloud";
|
|
||||||
};
|
|
||||||
distro = "nixos";
|
|
||||||
network = {
|
|
||||||
renderers = [ "networkd" ];
|
|
||||||
activators = [ "networkd" ];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
cloud_init_modules = [
|
|
||||||
"migrator"
|
|
||||||
"seed_random"
|
|
||||||
"bootcmd"
|
|
||||||
];
|
|
||||||
|
|
||||||
cloud_config_modules = [
|
|
||||||
"ssh-import-id"
|
|
||||||
"timezone"
|
|
||||||
"runcmd"
|
|
||||||
"ssh"
|
|
||||||
];
|
|
||||||
|
|
||||||
cloud_final_modules = [
|
|
||||||
"keys-to-console"
|
|
||||||
"final-message"
|
|
||||||
"power-state-change"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
services.openssh = {
|
|
||||||
enable = true;
|
|
||||||
hostKeys = [
|
|
||||||
{
|
|
||||||
path = "/persisted/etc/ssh/ssh_host_ed25519_key";
|
|
||||||
type = "ed25519";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
settings = {
|
|
||||||
PasswordAuthentication = false;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
users.users.root = {
|
|
||||||
home = lib.mkForce "/persisted/root";
|
|
||||||
};
|
|
||||||
|
|
||||||
networking.firewall.allowedTCPPorts = [ 80 443 ];
|
|
||||||
# UDP allowed for HTTP/3.
|
|
||||||
networking.firewall.allowedUDPPorts = [ 80 443 ];
|
|
||||||
|
|
||||||
services.caddy = {
|
|
||||||
enable = true;
|
|
||||||
|
|
||||||
globalConfig = ''
|
|
||||||
# Comment this if building the prod image. The following is only useful for testing.
|
|
||||||
local_certs
|
|
||||||
skip_install_trust
|
|
||||||
'';
|
|
||||||
|
|
||||||
virtualHosts."code.akols.com".extraConfig = ''
|
|
||||||
encode zstd gzip
|
|
||||||
reverse_proxy http://127.0.0.1:3000
|
|
||||||
|
|
||||||
# @exclude_lfs not path *.git/info/lfs*
|
|
||||||
|
|
||||||
# basic_auth @exclude_lfs {
|
|
||||||
# boardgamers $2a$14$Tni1.M8JUU4EXyWlVTL2jetDlWPamXtXZlYZizm2DtU.cwyLetbCm
|
|
||||||
# }
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
services.forgejo = {
|
|
||||||
enable = true;
|
|
||||||
|
|
||||||
package = pkgs.forgejo;
|
|
||||||
lfs.enable = true;
|
|
||||||
|
|
||||||
settings = {
|
|
||||||
service = {
|
|
||||||
DISABLE_REGISTRATION = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
database = {
|
|
||||||
SQLITE_JOURNAL_MODE = "WAL";
|
|
||||||
};
|
|
||||||
|
|
||||||
cache = {
|
|
||||||
ADAPTER = "twoqueue";
|
|
||||||
HOST = "{\"size\":100,\"recent_ratio\":0.25,\"ghost_ratio\":0.5}";
|
|
||||||
};
|
|
||||||
|
|
||||||
server = {
|
|
||||||
HTTP_ADDR = "127.0.0.1";
|
|
||||||
HTTP_PORT = 3000;
|
|
||||||
DOMAIN = "code.akols.com";
|
|
||||||
ROOT_URL = "https://code.akols.com";
|
|
||||||
};
|
|
||||||
|
|
||||||
session = {
|
|
||||||
COOKIE_SECURE = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
security = {
|
|
||||||
LOGIN_REMEMBER_DAYS = 365;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,89 +0,0 @@
|
||||||
{ lib, ... }:
|
|
||||||
{
|
|
||||||
disko.devices = {
|
|
||||||
disk.disk1 = {
|
|
||||||
device = lib.mkDefault "/dev/sda";
|
|
||||||
type = "disk";
|
|
||||||
content = {
|
|
||||||
type = "gpt";
|
|
||||||
partitions = {
|
|
||||||
boot = {
|
|
||||||
size = "1M";
|
|
||||||
type = "EF02";
|
|
||||||
priority = 1;
|
|
||||||
};
|
|
||||||
esp = {
|
|
||||||
name = "ESP";
|
|
||||||
size = "500M";
|
|
||||||
type = "EF00";
|
|
||||||
content = {
|
|
||||||
type = "filesystem";
|
|
||||||
format = "vfat";
|
|
||||||
mountpoint = "/boot";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
zfs = {
|
|
||||||
name = "zfs";
|
|
||||||
size = "100%";
|
|
||||||
content = {
|
|
||||||
type = "zfs";
|
|
||||||
pool = "zroot";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
zpool = {
|
|
||||||
zroot = {
|
|
||||||
type = "zpool";
|
|
||||||
mode = "";
|
|
||||||
# May cause issues if we end up adding any pools that will be imported before boot, but at the time this was added, we only had a "zroot" pool that was always imported at boot via `boot.zfs.extraPools`, so this was fine.
|
|
||||||
options.cachefile = "none";
|
|
||||||
rootFsOptions = {
|
|
||||||
compression = "zstd";
|
|
||||||
"com.sun:auto-snapshot" = "false";
|
|
||||||
canmount = "off";
|
|
||||||
};
|
|
||||||
mountpoint = null;
|
|
||||||
postCreateHook = ''
|
|
||||||
zfs list -t snapshot -H -o name | grep -E '^zroot/root@blank$' || zfs snapshot zroot/root@blank
|
|
||||||
'';
|
|
||||||
postMountHook = ''
|
|
||||||
mkdir -p /mnt/persisted/var/lib/systemd
|
|
||||||
mkdir -p /mnt/persisted/etc/ssh
|
|
||||||
mkdir -p /mnt/persisted/secrets
|
|
||||||
mkdir -p /mnt/persisted/root
|
|
||||||
'';
|
|
||||||
|
|
||||||
datasets = {
|
|
||||||
root = {
|
|
||||||
type = "zfs_fs";
|
|
||||||
mountpoint = "/";
|
|
||||||
options.canmount = "on";
|
|
||||||
};
|
|
||||||
nix = {
|
|
||||||
type = "zfs_fs";
|
|
||||||
mountpoint = "/nix";
|
|
||||||
options.canmount = "on";
|
|
||||||
};
|
|
||||||
replicated = {
|
|
||||||
type = "zfs_fs";
|
|
||||||
mountpoint = null;
|
|
||||||
options.canmount = "off";
|
|
||||||
};
|
|
||||||
"replicated/home" = {
|
|
||||||
type = "zfs_fs";
|
|
||||||
mountpoint = "/home";
|
|
||||||
options.canmount = "on";
|
|
||||||
};
|
|
||||||
"replicated/persisted" = {
|
|
||||||
type = "zfs_fs";
|
|
||||||
mountpoint = "/persisted";
|
|
||||||
options.canmount = "on";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
76
flake.lock
generated
76
flake.lock
generated
|
@ -1,53 +1,15 @@
|
||||||
{
|
{
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"disko": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1739841949,
|
|
||||||
"narHash": "sha256-lSOXdgW/1zi/SSu7xp71v+55D5Egz8ACv0STkj7fhbs=",
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "disko",
|
|
||||||
"rev": "15dbf8cebd8e2655a883b74547108e089f051bf0",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "disko",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"flake-parts": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs-lib": "nixpkgs-lib"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1738453229,
|
|
||||||
"narHash": "sha256-7H9XgNiGLKN1G1CgRh0vUL4AheZSYzPm+zmZ7vxbJdo=",
|
|
||||||
"owner": "hercules-ci",
|
|
||||||
"repo": "flake-parts",
|
|
||||||
"rev": "32ea77a06711b758da0ad9bd6a844c5740a87abd",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "hercules-ci",
|
|
||||||
"repo": "flake-parts",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"flake-utils": {
|
"flake-utils": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"systems": "systems"
|
"systems": "systems"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1731533236,
|
"lastModified": 1710146030,
|
||||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "flake-utils",
|
"repo": "flake-utils",
|
||||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -65,11 +27,11 @@
|
||||||
"rust-overlay": "rust-overlay"
|
"rust-overlay": "rust-overlay"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1732053863,
|
"lastModified": 1726716330,
|
||||||
"narHash": "sha256-DCIVdlb81Fct2uwzbtnawLBC/U03U2hqx8trqTJB7WA=",
|
"narHash": "sha256-mIuOP4I51eFLquRaxMKx67pHmhatZrcVPjfHL98v/M8=",
|
||||||
"owner": "oxalica",
|
"owner": "oxalica",
|
||||||
"repo": "nil",
|
"repo": "nil",
|
||||||
"rev": "2e24c9834e3bb5aa2a3701d3713b43a6fb106362",
|
"rev": "c8e8ce72442a164d89d3fdeaae0bcc405f8c015a",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -80,11 +42,11 @@
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1740126099,
|
"lastModified": 1727348695,
|
||||||
"narHash": "sha256-ozoOtE2hGsqh4XkTJFsrTkNxkRgShxpQxDynaPZUGxk=",
|
"narHash": "sha256-J+PeFKSDV+pHL7ukkfpVzCOO7mBSrrpJ3svwBFABbhI=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "32fb99ba93fea2798be0e997ea331dd78167f814",
|
"rev": "1925c603f17fc89f4c8f6bf6f631a802ad85d784",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -94,22 +56,8 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs-lib": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1738452942,
|
|
||||||
"narHash": "sha256-vJzFZGaCpnmo7I6i416HaBLpC+hvcURh/BQwROcGIp8=",
|
|
||||||
"type": "tarball",
|
|
||||||
"url": "https://github.com/NixOS/nixpkgs/archive/072a6db25e947df2f31aab9eccd0ab75d5b2da11.tar.gz"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"type": "tarball",
|
|
||||||
"url": "https://github.com/NixOS/nixpkgs/archive/072a6db25e947df2f31aab9eccd0ab75d5b2da11.tar.gz"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"disko": "disko",
|
|
||||||
"flake-parts": "flake-parts",
|
|
||||||
"nil": "nil",
|
"nil": "nil",
|
||||||
"nixpkgs": "nixpkgs"
|
"nixpkgs": "nixpkgs"
|
||||||
}
|
}
|
||||||
|
@ -122,11 +70,11 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1731983527,
|
"lastModified": 1726453838,
|
||||||
"narHash": "sha256-JECaBgC0pQ91Hq3W4unH6K9to8s2Zl2sPNu7bLOv4ek=",
|
"narHash": "sha256-pupsow4L79SBfNwT6vh/5RAbVZuhngIA0RTCZksXmZY=",
|
||||||
"owner": "oxalica",
|
"owner": "oxalica",
|
||||||
"repo": "rust-overlay",
|
"repo": "rust-overlay",
|
||||||
"rev": "71287228d96e9568e1e70c6bbfa3f992d145947b",
|
"rev": "ca2e79cd22625d214b8437c2c4080ce79bd9f7d2",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
56
flake.nix
56
flake.nix
|
@ -1,14 +1,8 @@
|
||||||
{
|
{
|
||||||
description = "Epesooj webring and machine.";
|
description = "Epesooj webring.";
|
||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||||
flake-parts.url = "github:hercules-ci/flake-parts";
|
|
||||||
|
|
||||||
disko = {
|
|
||||||
url = "github:nix-community/disko";
|
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
|
||||||
};
|
|
||||||
|
|
||||||
nil = {
|
nil = {
|
||||||
url = "github:oxalica/nil";
|
url = "github:oxalica/nil";
|
||||||
|
@ -16,55 +10,18 @@
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = inputs@{ flake-parts, nil, ... }:
|
outputs = { self, nixpkgs, nil, ... }:
|
||||||
flake-parts.lib.mkFlake { inherit inputs; } ({ config, getSystem, ... }: {
|
|
||||||
systems = [ "x86_64-linux" ];
|
|
||||||
|
|
||||||
imports = [
|
|
||||||
./code_server.nix
|
|
||||||
];
|
|
||||||
|
|
||||||
flake =
|
|
||||||
let
|
let
|
||||||
codeServerModules = [
|
pkgs = import nixpkgs {
|
||||||
config.flake.nixosModules.code-server
|
system = "x86_64-linux";
|
||||||
{
|
};
|
||||||
networking.hostName = "epesooj-code-0001";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
nixosConfigurations = {
|
devShells.x86_64-linux = {
|
||||||
code = inputs.nixpkgs.lib.nixosSystem {
|
|
||||||
system = "x86_64-linux";
|
|
||||||
modules = codeServerModules;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
colmena = {
|
|
||||||
meta.nixpkgs = import inputs.nixpkgs {
|
|
||||||
system = "x86_64-linux";
|
|
||||||
};
|
|
||||||
|
|
||||||
code = {
|
|
||||||
deployment = {
|
|
||||||
targetHost = "epesooj-code-0001"; # Requires an SSH config with information for this host.
|
|
||||||
};
|
|
||||||
|
|
||||||
imports = codeServerModules;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
perSystem = { system, pkgs, ... }: {
|
|
||||||
devShells = {
|
|
||||||
default = pkgs.mkShell {
|
default = pkgs.mkShell {
|
||||||
packages = with pkgs; [
|
packages = with pkgs; [
|
||||||
just
|
|
||||||
nodePackages_latest.nodejs
|
nodePackages_latest.nodejs
|
||||||
|
|
||||||
colmena
|
|
||||||
|
|
||||||
# Both of these used with VSCode.
|
# Both of these used with VSCode.
|
||||||
nixpkgs-fmt
|
nixpkgs-fmt
|
||||||
nil.packages.${system}.default
|
nil.packages.${system}.default
|
||||||
|
@ -72,5 +29,4 @@
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
8
justfile
8
justfile
|
@ -1,8 +0,0 @@
|
||||||
default:
|
|
||||||
@just --list --justfile {{justfile()}}
|
|
||||||
|
|
||||||
build_nixos_config host_type="code":
|
|
||||||
nix build .#nixosConfigurations.{{host_type}}.config.system.build.toplevel
|
|
||||||
|
|
||||||
nixify_host hostname host_type="code":
|
|
||||||
nix run github:nix-community/nixos-anywhere -- --flake .#{{host_type}} --target-host root@{{hostname}}
|
|
|
@ -34,23 +34,5 @@
|
||||||
"title": "The blog of protondonor",
|
"title": "The blog of protondonor",
|
||||||
"author": "protondonor",
|
"author": "protondonor",
|
||||||
"url": "https://blog.proton.gay/"
|
"url": "https://blog.proton.gay/"
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "phil",
|
|
||||||
"title": "phil's web site",
|
|
||||||
"author": "Phil Giammattei",
|
|
||||||
"url": "https://phils-web-site.net"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "mira",
|
|
||||||
"title": "Mira Welner's site",
|
|
||||||
"author": "Mira Welner",
|
|
||||||
"url": "https://mirawelner.com"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "graphicalmethods",
|
|
||||||
"title": "Data Collection",
|
|
||||||
"author": "graphdev",
|
|
||||||
"url": "https://blog.graphicalmethods.com"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
Loading…
Add table
Reference in a new issue