merge main
This commit is contained in:
24
checks/deltachat/default.nix
Normal file
24
checks/deltachat/default.nix
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
(import ../lib/container-test.nix) ({ pkgs, ... }: {
|
||||||
|
name = "secrets";
|
||||||
|
|
||||||
|
nodes.machine = { self, ... }: {
|
||||||
|
imports = [
|
||||||
|
self.clanModules.deltachat
|
||||||
|
self.nixosModules.clanCore
|
||||||
|
{
|
||||||
|
clanCore.machineName = "machine";
|
||||||
|
clanCore.clanDir = ./.;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
testScript = ''
|
||||||
|
start_all()
|
||||||
|
machine.wait_for_unit("maddy")
|
||||||
|
# imap
|
||||||
|
machine.succeed("${pkgs.netcat}/bin/nc -z -v ::1 143")
|
||||||
|
# smtp submission
|
||||||
|
machine.succeed("${pkgs.netcat}/bin/nc -z -v ::1 587")
|
||||||
|
# smtp
|
||||||
|
machine.succeed("${pkgs.netcat}/bin/nc -z -v ::1 25")
|
||||||
|
'';
|
||||||
|
})
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
# import our test
|
# import our test
|
||||||
secrets = import ./secrets nixosTestArgs;
|
secrets = import ./secrets nixosTestArgs;
|
||||||
container = import ./container nixosTestArgs;
|
container = import ./container nixosTestArgs;
|
||||||
|
deltachat = import ./deltachat nixosTestArgs;
|
||||||
};
|
};
|
||||||
schemaTests = pkgs.callPackages ./schemas.nix {
|
schemaTests = pkgs.callPackages ./schemas.nix {
|
||||||
inherit self;
|
inherit self;
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
{ extraPythonPackages, buildPythonApplication, self, setuptools, util-linux, systemd }:
|
{ extraPythonPackages, python3Packages, buildPythonApplication, setuptools, util-linux, systemd }:
|
||||||
buildPythonApplication {
|
buildPythonApplication {
|
||||||
pname = "test-driver";
|
pname = "test-driver";
|
||||||
version = "0.0.1";
|
version = "0.0.1";
|
||||||
propagatedBuildInputs = [ util-linux systemd ] ++ extraPythonPackages self;
|
propagatedBuildInputs = [ util-linux systemd ] ++ extraPythonPackages python3Packages;
|
||||||
nativeBuildInputs = [ setuptools ];
|
nativeBuildInputs = [ setuptools ];
|
||||||
format = "pyproject";
|
format = "pyproject";
|
||||||
src = ./.;
|
src = ./.;
|
||||||
|
|||||||
@@ -21,11 +21,6 @@ line-length = 88
|
|||||||
select = ["E", "F", "I", "U", "N"]
|
select = ["E", "F", "I", "U", "N"]
|
||||||
ignore = ["E501"]
|
ignore = ["E501"]
|
||||||
|
|
||||||
[tool.black]
|
|
||||||
line-length = 88
|
|
||||||
target-version = ['py39']
|
|
||||||
include = '\.pyi?$'
|
|
||||||
|
|
||||||
[tool.mypy]
|
[tool.mypy]
|
||||||
python_version = "3.10"
|
python_version = "3.10"
|
||||||
warn_redundant_casts = true
|
warn_redundant_casts = true
|
||||||
|
|||||||
140
clanModules/deltachat.nix
Normal file
140
clanModules/deltachat.nix
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
{ config, pkgs, ... }: {
|
||||||
|
networking.firewall.interfaces."zt+".allowedTCPPorts = [ 25 ]; # smtp with other hosts
|
||||||
|
environment.systemPackages = [ pkgs.deltachat-desktop ];
|
||||||
|
|
||||||
|
services.maddy = {
|
||||||
|
enable = true;
|
||||||
|
primaryDomain = "${config.clanCore.machineName}.local";
|
||||||
|
config = ''
|
||||||
|
# Minimal configuration with TLS disabled, adapted from upstream example
|
||||||
|
# configuration here https://github.com/foxcpp/maddy/blob/master/maddy.conf
|
||||||
|
# Do not use this in unencrypted networks!
|
||||||
|
|
||||||
|
auth.pass_table local_authdb {
|
||||||
|
table sql_table {
|
||||||
|
driver sqlite3
|
||||||
|
dsn credentials.db
|
||||||
|
table_name passwords
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
storage.imapsql local_mailboxes {
|
||||||
|
driver sqlite3
|
||||||
|
dsn imapsql.db
|
||||||
|
}
|
||||||
|
|
||||||
|
table.chain local_rewrites {
|
||||||
|
optional_step regexp "(.+)\+(.+)@(.+)" "$1@$3"
|
||||||
|
optional_step static {
|
||||||
|
entry postmaster postmaster@$(primary_domain)
|
||||||
|
}
|
||||||
|
optional_step file /etc/maddy/aliases
|
||||||
|
}
|
||||||
|
|
||||||
|
msgpipeline local_routing {
|
||||||
|
destination postmaster $(local_domains) {
|
||||||
|
modify {
|
||||||
|
replace_rcpt &local_rewrites
|
||||||
|
}
|
||||||
|
deliver_to &local_mailboxes
|
||||||
|
}
|
||||||
|
default_destination {
|
||||||
|
reject 550 5.1.1 "User doesn't exist"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
smtp tcp://[::]:25 {
|
||||||
|
limits {
|
||||||
|
all rate 20 1s
|
||||||
|
all concurrency 10
|
||||||
|
}
|
||||||
|
dmarc yes
|
||||||
|
check {
|
||||||
|
require_mx_record
|
||||||
|
dkim
|
||||||
|
spf
|
||||||
|
}
|
||||||
|
source $(local_domains) {
|
||||||
|
reject 501 5.1.8 "Use Submission for outgoing SMTP"
|
||||||
|
}
|
||||||
|
default_source {
|
||||||
|
destination postmaster $(local_domains) {
|
||||||
|
deliver_to &local_routing
|
||||||
|
}
|
||||||
|
default_destination {
|
||||||
|
reject 550 5.1.1 "User doesn't exist"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
submission tcp://[::1]:587 {
|
||||||
|
limits {
|
||||||
|
all rate 50 1s
|
||||||
|
}
|
||||||
|
auth &local_authdb
|
||||||
|
source $(local_domains) {
|
||||||
|
check {
|
||||||
|
authorize_sender {
|
||||||
|
prepare_email &local_rewrites
|
||||||
|
user_to_email identity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
destination postmaster $(local_domains) {
|
||||||
|
deliver_to &local_routing
|
||||||
|
}
|
||||||
|
default_destination {
|
||||||
|
modify {
|
||||||
|
dkim $(primary_domain) $(local_domains) default
|
||||||
|
}
|
||||||
|
deliver_to &remote_queue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default_source {
|
||||||
|
reject 501 5.1.8 "Non-local sender domain"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
target.remote outbound_delivery {
|
||||||
|
limits {
|
||||||
|
destination rate 20 1s
|
||||||
|
destination concurrency 10
|
||||||
|
}
|
||||||
|
mx_auth {
|
||||||
|
dane
|
||||||
|
mtasts {
|
||||||
|
cache fs
|
||||||
|
fs_dir mtasts_cache/
|
||||||
|
}
|
||||||
|
local_policy {
|
||||||
|
min_tls_level encrypted
|
||||||
|
min_mx_level none
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
target.queue remote_queue {
|
||||||
|
target &outbound_delivery
|
||||||
|
autogenerated_msg_domain $(primary_domain)
|
||||||
|
bounce {
|
||||||
|
destination postmaster $(local_domains) {
|
||||||
|
deliver_to &local_routing
|
||||||
|
}
|
||||||
|
default_destination {
|
||||||
|
reject 550 5.0.0 "Refusing to send DSNs to non-local addresses"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
imap tcp://[::1]:143 {
|
||||||
|
auth &local_authdb
|
||||||
|
storage &local_mailboxes
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
ensureAccounts = [
|
||||||
|
"user@${config.clanCore.machineName}.local"
|
||||||
|
];
|
||||||
|
ensureCredentials = {
|
||||||
|
"user@${config.clanCore.machineName}.local".passwordFile = pkgs.writeText "dummy" "foobar";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
{ pkgs, ... }: {
|
|
||||||
environment.systemPackages = [ pkgs.dino ];
|
|
||||||
}
|
|
||||||
@@ -1,199 +0,0 @@
|
|||||||
{ config
|
|
||||||
, ...
|
|
||||||
}: {
|
|
||||||
services.ejabberd = {
|
|
||||||
enable = true;
|
|
||||||
configFile = "/etc/ejabberd.yml";
|
|
||||||
};
|
|
||||||
|
|
||||||
environment.etc."ejabberd.yml" = {
|
|
||||||
user = "ejabberd";
|
|
||||||
mode = "0600";
|
|
||||||
text = ''
|
|
||||||
loglevel: 4
|
|
||||||
|
|
||||||
default_db: sql
|
|
||||||
new_sql_schema: true
|
|
||||||
sql_type: sqlite
|
|
||||||
sql_database: "/var/lib/ejabberd/db.sqlite"
|
|
||||||
|
|
||||||
hosts:
|
|
||||||
- ${config.clanCore.machineName}.local
|
|
||||||
|
|
||||||
listen:
|
|
||||||
-
|
|
||||||
port: 5222
|
|
||||||
ip: "::1"
|
|
||||||
module: ejabberd_c2s
|
|
||||||
max_stanza_size: 262144
|
|
||||||
shaper: c2s_shaper
|
|
||||||
access: c2s
|
|
||||||
starttls_required: false
|
|
||||||
-
|
|
||||||
port: 5269
|
|
||||||
ip: "::"
|
|
||||||
module: ejabberd_s2s_in
|
|
||||||
max_stanza_size: 524288
|
|
||||||
|
|
||||||
auth_method: [anonymous]
|
|
||||||
anonymous_protocol: login_anon
|
|
||||||
acl:
|
|
||||||
local:
|
|
||||||
user_regexp: ""
|
|
||||||
loopback:
|
|
||||||
ip:
|
|
||||||
- 127.0.0.0/8
|
|
||||||
- ::1/128
|
|
||||||
access_rules:
|
|
||||||
local:
|
|
||||||
allow: local
|
|
||||||
c2s:
|
|
||||||
deny: blocked
|
|
||||||
allow: all
|
|
||||||
s2s:
|
|
||||||
- allow
|
|
||||||
announce:
|
|
||||||
allow: admin
|
|
||||||
configure:
|
|
||||||
allow: admin
|
|
||||||
muc_create:
|
|
||||||
allow: all
|
|
||||||
pubsub_createnode:
|
|
||||||
allow: local
|
|
||||||
trusted_network:
|
|
||||||
allow: loopback
|
|
||||||
api_permissions:
|
|
||||||
"console commands":
|
|
||||||
from:
|
|
||||||
- ejabberd_ctl
|
|
||||||
who: all
|
|
||||||
what: "*"
|
|
||||||
"admin access":
|
|
||||||
who:
|
|
||||||
access:
|
|
||||||
allow:
|
|
||||||
acl: loopback
|
|
||||||
acl: admin
|
|
||||||
oauth:
|
|
||||||
scope: "ejabberd:admin"
|
|
||||||
access:
|
|
||||||
allow:
|
|
||||||
acl: loopback
|
|
||||||
acl: admin
|
|
||||||
what:
|
|
||||||
- "*"
|
|
||||||
- "!stop"
|
|
||||||
- "!start"
|
|
||||||
"public commands":
|
|
||||||
who:
|
|
||||||
ip: 127.0.0.1/8
|
|
||||||
what:
|
|
||||||
- status
|
|
||||||
- connected_users_number
|
|
||||||
shaper:
|
|
||||||
normal: 1000
|
|
||||||
fast: 50000
|
|
||||||
|
|
||||||
shaper_rules:
|
|
||||||
max_user_sessions: 10
|
|
||||||
max_user_offline_messages:
|
|
||||||
5000: admin
|
|
||||||
100: all
|
|
||||||
c2s_shaper:
|
|
||||||
none: admin
|
|
||||||
normal: all
|
|
||||||
s2s_shaper: fast
|
|
||||||
modules:
|
|
||||||
mod_adhoc: {}
|
|
||||||
mod_admin_extra: {}
|
|
||||||
mod_announce:
|
|
||||||
access: announce
|
|
||||||
mod_avatar: {}
|
|
||||||
mod_blocking: {}
|
|
||||||
mod_bosh: {}
|
|
||||||
mod_caps: {}
|
|
||||||
mod_carboncopy: {}
|
|
||||||
mod_client_state: {}
|
|
||||||
mod_configure: {}
|
|
||||||
mod_disco: {}
|
|
||||||
mod_fail2ban: {}
|
|
||||||
mod_http_api: {}
|
|
||||||
mod_http_upload:
|
|
||||||
put_url: https://@HOST@:5443/upload
|
|
||||||
mod_last: {}
|
|
||||||
mod_mam:
|
|
||||||
## Mnesia is limited to 2GB, better to use an SQL backend
|
|
||||||
## For small servers SQLite is a good fit and is very easy
|
|
||||||
## to configure. Uncomment this when you have SQL configured:
|
|
||||||
## db_type: sql
|
|
||||||
assume_mam_usage: true
|
|
||||||
default: always
|
|
||||||
mod_mqtt:
|
|
||||||
access_publish:
|
|
||||||
"homeassistant/#":
|
|
||||||
- allow: hass_publisher
|
|
||||||
- deny
|
|
||||||
"#":
|
|
||||||
- deny
|
|
||||||
access_subscribe:
|
|
||||||
"homeassistant/#":
|
|
||||||
- allow: hass_subscriber
|
|
||||||
- deny
|
|
||||||
"#":
|
|
||||||
- deny
|
|
||||||
mod_muc:
|
|
||||||
host: "muc.@HOST@"
|
|
||||||
access:
|
|
||||||
- allow
|
|
||||||
access_admin:
|
|
||||||
- allow: admin
|
|
||||||
access_create: muc_create
|
|
||||||
access_persistent: muc_create
|
|
||||||
access_mam:
|
|
||||||
- allow
|
|
||||||
default_room_options:
|
|
||||||
mam: true
|
|
||||||
mod_muc_admin: {}
|
|
||||||
mod_offline:
|
|
||||||
access_max_user_messages: max_user_offline_messages
|
|
||||||
mod_ping: {}
|
|
||||||
mod_privacy: {}
|
|
||||||
mod_private: {}
|
|
||||||
mod_proxy65:
|
|
||||||
access: local
|
|
||||||
max_connections: 5
|
|
||||||
mod_pubsub:
|
|
||||||
access_createnode: pubsub_createnode
|
|
||||||
plugins:
|
|
||||||
- flat
|
|
||||||
- pep
|
|
||||||
force_node_config:
|
|
||||||
## Avoid buggy clients to make their bookmarks public
|
|
||||||
storage:bookmarks:
|
|
||||||
access_model: whitelist
|
|
||||||
mod_push: {}
|
|
||||||
mod_push_keepalive: {}
|
|
||||||
mod_register:
|
|
||||||
## Only accept registration requests from the "trusted"
|
|
||||||
## network (see access_rules section above).
|
|
||||||
## Think twice before enabling registration from any
|
|
||||||
## address. See the Jabber SPAM Manifesto for details:
|
|
||||||
## https://github.com/ge0rg/jabber-spam-fighting-manifesto
|
|
||||||
ip_access: trusted_network
|
|
||||||
mod_roster:
|
|
||||||
versioning: true
|
|
||||||
mod_s2s_dialback: {}
|
|
||||||
mod_shared_roster: {}
|
|
||||||
mod_stream_mgmt:
|
|
||||||
resend_on_timeout: if_offline
|
|
||||||
mod_vcard: {}
|
|
||||||
mod_vcard_xupdate: {}
|
|
||||||
mod_version:
|
|
||||||
show_os: false
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
networking.firewall.allowedTCPPorts = [
|
|
||||||
5269 # xmpp-server
|
|
||||||
];
|
|
||||||
}
|
|
||||||
@@ -8,8 +8,7 @@
|
|||||||
];
|
];
|
||||||
})
|
})
|
||||||
(builtins.readDir ./diskLayouts);
|
(builtins.readDir ./diskLayouts);
|
||||||
ejabberd = ./ejabberd.nix;
|
deltachat = ./deltachat.nix;
|
||||||
dino = ./dino.nix;
|
|
||||||
xfce = ./xfce.nix;
|
xfce = ./xfce.nix;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,11 @@ Welcome to our website template repository! This template is designed to help yo
|
|||||||
|
|
||||||
**Dependency Management**: We use the [Nix package manager](https://nixos.org/) to manage dependencies and ensure reproducibility, making your development process more robust.
|
**Dependency Management**: We use the [Nix package manager](https://nixos.org/) to manage dependencies and ensure reproducibility, making your development process more robust.
|
||||||
|
|
||||||
|
## Supported Operating Systems
|
||||||
|
|
||||||
|
- Linux
|
||||||
|
- macOS
|
||||||
|
|
||||||
# Getting Started with the Development Environment
|
# Getting Started with the Development Environment
|
||||||
|
|
||||||
Let's get your development environment up and running:
|
Let's get your development environment up and running:
|
||||||
@@ -28,11 +33,20 @@ Let's get your development environment up and running:
|
|||||||
curl -sfL https://direnv.net/install.sh | bash
|
curl -sfL https://direnv.net/install.sh | bash
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **Clone the Repository and Navigate**:
|
3. **Add direnv to your shell**:
|
||||||
|
|
||||||
|
- Direnv needs to [hook into your shell](https://direnv.net/docs/hook.html) to work.
|
||||||
|
You can do this by executing following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
echo 'eval "$(direnv hook zsh)"' >> ~/.zshrc && echo 'eval "$(direnv hook bash)"' >> ~/.bashrc && eval "$SHELL"
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Clone the Repository and Navigate**:
|
||||||
|
|
||||||
- Clone this repository and navigate to it.
|
- Clone this repository and navigate to it.
|
||||||
|
|
||||||
4. **Allow .envrc**:
|
5. **Allow .envrc**:
|
||||||
|
|
||||||
- When you enter the directory, you'll receive an error message like this:
|
- When you enter the directory, you'll receive an error message like this:
|
||||||
```bash
|
```bash
|
||||||
@@ -40,7 +54,7 @@ Let's get your development environment up and running:
|
|||||||
```
|
```
|
||||||
- Execute `direnv allow` to automatically execute the shell script `.envrc` when entering the directory.
|
- Execute `direnv allow` to automatically execute the shell script `.envrc` when entering the directory.
|
||||||
|
|
||||||
5. **Build the Backend**:
|
6. **Build the Backend**:
|
||||||
|
|
||||||
- Go to the `pkgs/clan-cli` directory and execute:
|
- Go to the `pkgs/clan-cli` directory and execute:
|
||||||
```bash
|
```bash
|
||||||
@@ -48,7 +62,7 @@ Let's get your development environment up and running:
|
|||||||
```
|
```
|
||||||
- Wait for the backend to build.
|
- Wait for the backend to build.
|
||||||
|
|
||||||
6. **Start the Backend Server**:
|
7. **Start the Backend Server**:
|
||||||
|
|
||||||
- To start the backend server, execute:
|
- To start the backend server, execute:
|
||||||
```bash
|
```bash
|
||||||
@@ -56,7 +70,7 @@ Let's get your development environment up and running:
|
|||||||
```
|
```
|
||||||
- The server will automatically restart if any Python files change.
|
- The server will automatically restart if any Python files change.
|
||||||
|
|
||||||
7. **Build the Frontend**:
|
8. **Build the Frontend**:
|
||||||
|
|
||||||
- In a different shell, navigate to the `pkgs/ui` directory and execute:
|
- In a different shell, navigate to the `pkgs/ui` directory and execute:
|
||||||
```bash
|
```bash
|
||||||
@@ -64,7 +78,7 @@ Let's get your development environment up and running:
|
|||||||
```
|
```
|
||||||
- Wait for the frontend to build.
|
- Wait for the frontend to build.
|
||||||
|
|
||||||
8. **Start the Frontend**:
|
9. **Start the Frontend**:
|
||||||
- To start the frontend, execute:
|
- To start the frontend, execute:
|
||||||
```bash
|
```bash
|
||||||
npm run dev
|
npm run dev
|
||||||
@@ -194,4 +208,4 @@ To make the most of this template:
|
|||||||
- Set the option to "Delete pull request branch after merge by default."
|
- Set the option to "Delete pull request branch after merge by default."
|
||||||
- Also, set the default merge style to "Rebase then create merge commit."
|
- Also, set the default merge style to "Rebase then create merge commit."
|
||||||
|
|
||||||
With this template, you're well-equipped to build and collaborate on high-quality websites efficiently. Happy coding!
|
With this template, you're well-equipped to build and collaborate on high-quality websites efficiently. Happy coding!.
|
||||||
|
|||||||
32
flake.lock
generated
32
flake.lock
generated
@@ -7,11 +7,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1697995812,
|
"lastModified": 1698422527,
|
||||||
"narHash": "sha256-UDlK6p/6vAiVOQ92PR0ySDZBS3yiryrlJpSOw3b9Ito=",
|
"narHash": "sha256-SDu3Xg263t3oXIyTaH0buOvFnKIDeZsvKDBtOz+jRbs=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "disko",
|
"repo": "disko",
|
||||||
"rev": "4122a18340094151d7911e838237ec7627f0d0c5",
|
"rev": "944d338d24a9d043a3f7461c30ee6cfe4f9cca30",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -27,11 +27,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1696343447,
|
"lastModified": 1698882062,
|
||||||
"narHash": "sha256-B2xAZKLkkeRFG5XcHHSXXcP7To9Xzr59KXeZiRf4vdQ=",
|
"narHash": "sha256-HkhafUayIqxXyHH1X8d9RDl1M2CkFgZLjKD3MzabiEo=",
|
||||||
"owner": "hercules-ci",
|
"owner": "hercules-ci",
|
||||||
"repo": "flake-parts",
|
"repo": "flake-parts",
|
||||||
"rev": "c9afaba3dfa4085dbd2ccb38dfade5141e33d9d4",
|
"rev": "8c9fa2545007b49a5db5f650ae91f227672c3877",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -98,16 +98,16 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1696051733,
|
"lastModified": 1699007274,
|
||||||
"narHash": "sha256-fEC8/6wJOWgCSvBjPwMBdaYtp57OUfQd3dJgp0D/It4=",
|
"narHash": "sha256-m0NH2trnW8cOhona6m3hWkeDZ28BV/wAGPd/YWik23g=",
|
||||||
"owner": "Mic92",
|
"owner": "Mic92",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "c3bd4f19ef0062d4462444aa413e26c917187ae9",
|
"rev": "fcb19bae00e9d3fd5ecf4a1f80cf33248bf7f714",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "Mic92",
|
"owner": "Mic92",
|
||||||
"ref": "fakeroot",
|
"ref": "deltachat",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
@@ -131,11 +131,11 @@
|
|||||||
"nixpkgs-stable": []
|
"nixpkgs-stable": []
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1697943852,
|
"lastModified": 1699021419,
|
||||||
"narHash": "sha256-DaBxUPaZhQ3yLCmAATshYB7qo7NwcMvSFWz9T3bjYYY=",
|
"narHash": "sha256-oy2j2OHXYcckifASMeZzpmbDLSvobMGt0V/RvoDotF4=",
|
||||||
"owner": "Mic92",
|
"owner": "Mic92",
|
||||||
"repo": "sops-nix",
|
"repo": "sops-nix",
|
||||||
"rev": "30a0ba4a20703b4bfe047fe5def1fc24978e322c",
|
"rev": "275b28593ef3a1b9d05b6eeda3ddce2f45f5c06f",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -151,11 +151,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1697388351,
|
"lastModified": 1698438538,
|
||||||
"narHash": "sha256-63N2eBpKaziIy4R44vjpUu8Nz5fCJY7okKrkixvDQmY=",
|
"narHash": "sha256-AWxaKTDL3MtxaVTVU5lYBvSnlspOS0Fjt8GxBgnU0Do=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "treefmt-nix",
|
"repo": "treefmt-nix",
|
||||||
"rev": "aae39f64f5ecbe89792d05eacea5cb241891292a",
|
"rev": "5deb8dc125a9f83b65ca86cf0c8167c46593e0b1",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
@@ -5,9 +5,9 @@
|
|||||||
nixConfig.extra-trusted-public-keys = [ "cache.clan.lol-1:3KztgSAB5R1M+Dz7vzkBGzXdodizbgLXGXKXlcQLA28=" ];
|
nixConfig.extra-trusted-public-keys = [ "cache.clan.lol-1:3KztgSAB5R1M+Dz7vzkBGzXdodizbgLXGXKXlcQLA28=" ];
|
||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
#nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
#nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable-small";
|
||||||
# https://github.com/NixOS/nixpkgs/pull/257462
|
# https://github.com/NixOS/nixpkgs/pull/265024
|
||||||
nixpkgs.url = "github:Mic92/nixpkgs/fakeroot";
|
nixpkgs.url = "github:Mic92/nixpkgs/deltachat";
|
||||||
floco.url = "github:aakropotkin/floco";
|
floco.url = "github:aakropotkin/floco";
|
||||||
floco.inputs.nixpkgs.follows = "nixpkgs";
|
floco.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
disko.url = "github:nix-community/disko";
|
disko.url = "github:nix-community/disko";
|
||||||
|
|||||||
@@ -46,7 +46,7 @@
|
|||||||
"-eucx"
|
"-eucx"
|
||||||
''
|
''
|
||||||
${lib.getExe pkgs.ruff} --fix "$@"
|
${lib.getExe pkgs.ruff} --fix "$@"
|
||||||
${lib.getExe pkgs.black} "$@"
|
${lib.getExe pkgs.ruff} format "$@"
|
||||||
''
|
''
|
||||||
"--" # this argument is ignored by bash
|
"--" # this argument is ignored by bash
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import argparse
|
|||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
from typing import Optional
|
from typing import Any, Optional, Sequence
|
||||||
|
|
||||||
from . import config, flakes, join, machines, secrets, vms, webui
|
from . import config, flakes, join, machines, secrets, vms, webui
|
||||||
from .custom_logger import setup_logging
|
from .custom_logger import setup_logging
|
||||||
@@ -17,6 +17,24 @@ except ImportError:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class AppendOptionAction(argparse.Action):
|
||||||
|
def __init__(self, option_strings: str, dest: str, **kwargs: Any) -> None:
|
||||||
|
super().__init__(option_strings, dest, **kwargs)
|
||||||
|
|
||||||
|
def __call__(
|
||||||
|
self,
|
||||||
|
parser: argparse.ArgumentParser,
|
||||||
|
namespace: argparse.Namespace,
|
||||||
|
values: str | Sequence[str] | None,
|
||||||
|
option_string: Optional[str] = None,
|
||||||
|
) -> None:
|
||||||
|
lst = getattr(namespace, self.dest)
|
||||||
|
lst.append("--option")
|
||||||
|
assert isinstance(values, list), "values must be a list"
|
||||||
|
lst.append(values[0])
|
||||||
|
lst.append(values[1])
|
||||||
|
|
||||||
|
|
||||||
def create_parser(prog: Optional[str] = None) -> argparse.ArgumentParser:
|
def create_parser(prog: Optional[str] = None) -> argparse.ArgumentParser:
|
||||||
parser = argparse.ArgumentParser(prog=prog, description="cLAN tool")
|
parser = argparse.ArgumentParser(prog=prog, description="cLAN tool")
|
||||||
|
|
||||||
@@ -26,6 +44,15 @@ def create_parser(prog: Optional[str] = None) -> argparse.ArgumentParser:
|
|||||||
action="store_true",
|
action="store_true",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--option",
|
||||||
|
help="Nix option to set",
|
||||||
|
nargs=2,
|
||||||
|
metavar=("name", "value"),
|
||||||
|
action=AppendOptionAction,
|
||||||
|
default=[],
|
||||||
|
)
|
||||||
|
|
||||||
subparsers = parser.add_subparsers()
|
subparsers = parser.add_subparsers()
|
||||||
|
|
||||||
parser_flake = subparsers.add_parser(
|
parser_flake = subparsers.add_parser(
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
from .create import register_create_parser
|
from .create import register_create_parser
|
||||||
from .list import register_list_parser
|
from .list_flakes import register_list_parser
|
||||||
|
|
||||||
|
|
||||||
# takes a (sub)parser and configures it
|
# takes a (sub)parser and configures it
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import NewType
|
from typing import NewType, Union
|
||||||
|
|
||||||
|
from pydantic import AnyUrl
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
FlakeName = NewType("FlakeName", str)
|
FlakeName = NewType("FlakeName", str)
|
||||||
|
|
||||||
|
FlakeUrl = Union[AnyUrl, Path]
|
||||||
|
|
||||||
|
|
||||||
def validate_path(base_dir: Path, value: Path) -> Path:
|
def validate_path(base_dir: Path, value: Path) -> Path:
|
||||||
user_path = (base_dir / value).resolve()
|
user_path = (base_dir / value).resolve()
|
||||||
|
|||||||
@@ -11,29 +11,23 @@ from typing import Iterator
|
|||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
from ..dirs import clan_flakes_dir, specific_flake_dir
|
from ..dirs import clan_flakes_dir, specific_flake_dir
|
||||||
from ..errors import ClanError
|
|
||||||
from ..nix import nix_build, nix_config, nix_eval, nix_shell
|
from ..nix import nix_build, nix_config, nix_eval, nix_shell
|
||||||
from ..task_manager import BaseTask, Command, create_task
|
from ..task_manager import BaseTask, Command, create_task
|
||||||
from ..types import validate_path
|
from ..types import validate_path
|
||||||
from .inspect import VmConfig, inspect_vm
|
from .inspect import VmConfig, inspect_vm
|
||||||
|
|
||||||
|
|
||||||
def is_path_or_url(s: str) -> str | None:
|
def is_flake_url(s: str) -> bool:
|
||||||
# check if s is a valid path
|
if re.match(r"^http.?://[a-zA-Z0-9.-]+/[a-zA-Z0-9.-]+", s) is not None:
|
||||||
if os.path.exists(s):
|
return True
|
||||||
return "path"
|
return False
|
||||||
# check if s is a valid URL
|
|
||||||
elif re.match(r"^https?://[a-zA-Z0-9.-]+/[a-zA-Z0-9.-]+", s):
|
|
||||||
return "URL"
|
|
||||||
# otherwise, return None
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
class BuildVmTask(BaseTask):
|
class BuildVmTask(BaseTask):
|
||||||
def __init__(self, uuid: UUID, vm: VmConfig) -> None:
|
def __init__(self, uuid: UUID, vm: VmConfig, nix_options: list[str] = []) -> None:
|
||||||
super().__init__(uuid, num_cmds=7)
|
super().__init__(uuid, num_cmds=7)
|
||||||
self.vm = vm
|
self.vm = vm
|
||||||
|
self.nix_options = nix_options
|
||||||
|
|
||||||
def get_vm_create_info(self, cmds: Iterator[Command]) -> dict:
|
def get_vm_create_info(self, cmds: Iterator[Command]) -> dict:
|
||||||
config = nix_config()
|
config = nix_config()
|
||||||
@@ -47,6 +41,7 @@ class BuildVmTask(BaseTask):
|
|||||||
[
|
[
|
||||||
f'{clan_dir}#clanInternals.machines."{system}"."{machine}".config.system.clan.vm.create'
|
f'{clan_dir}#clanInternals.machines."{system}"."{machine}".config.system.clan.vm.create'
|
||||||
]
|
]
|
||||||
|
+ self.nix_options
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
vm_json = "".join(cmd.stdout).strip()
|
vm_json = "".join(cmd.stdout).strip()
|
||||||
@@ -57,7 +52,7 @@ class BuildVmTask(BaseTask):
|
|||||||
def get_clan_name(self, cmds: Iterator[Command]) -> str:
|
def get_clan_name(self, cmds: Iterator[Command]) -> str:
|
||||||
clan_dir = self.vm.flake_url
|
clan_dir = self.vm.flake_url
|
||||||
cmd = next(cmds)
|
cmd = next(cmds)
|
||||||
cmd.run(nix_eval([f"{clan_dir}#clanInternals.clanName"]))
|
cmd.run(nix_eval([f"{clan_dir}#clanInternals.clanName"]) + self.nix_options)
|
||||||
clan_name = cmd.stdout[0].strip().strip('"')
|
clan_name = cmd.stdout[0].strip().strip('"')
|
||||||
return clan_name
|
return clan_name
|
||||||
|
|
||||||
@@ -93,12 +88,8 @@ class BuildVmTask(BaseTask):
|
|||||||
) # TODO do this in the clanCore module
|
) # TODO do this in the clanCore module
|
||||||
env["SECRETS_DIR"] = str(secrets_dir)
|
env["SECRETS_DIR"] = str(secrets_dir)
|
||||||
|
|
||||||
res = is_path_or_url(str(self.vm.flake_url))
|
# Only generate secrets for local clans
|
||||||
if res is None:
|
if not is_flake_url(str(self.vm.flake_url)):
|
||||||
raise ClanError(
|
|
||||||
f"flake_url must be a valid path or URL, got {self.vm.flake_url}"
|
|
||||||
)
|
|
||||||
elif res == "path": # Only generate secrets for local clans
|
|
||||||
cmd = next(cmds)
|
cmd = next(cmds)
|
||||||
if Path(self.vm.flake_url).is_dir():
|
if Path(self.vm.flake_url).is_dir():
|
||||||
cmd.run(
|
cmd.run(
|
||||||
@@ -151,27 +142,44 @@ class BuildVmTask(BaseTask):
|
|||||||
"console=tty0",
|
"console=tty0",
|
||||||
]
|
]
|
||||||
qemu_command = [
|
qemu_command = [
|
||||||
# fmt: off
|
|
||||||
"qemu-kvm",
|
"qemu-kvm",
|
||||||
"-name", machine,
|
"-name",
|
||||||
"-m", f'{vm_config["memorySize"]}M',
|
machine,
|
||||||
"-smp", str(vm_config["cores"]),
|
"-m",
|
||||||
"-device", "virtio-rng-pci",
|
f'{vm_config["memorySize"]}M',
|
||||||
"-net", "nic,netdev=user.0,model=virtio", "-netdev", "user,id=user.0",
|
"-smp",
|
||||||
"-virtfs", "local,path=/nix/store,security_model=none,mount_tag=nix-store",
|
str(vm_config["cores"]),
|
||||||
"-virtfs", f"local,path={xchg_dir},security_model=none,mount_tag=shared",
|
"-device",
|
||||||
"-virtfs", f"local,path={xchg_dir},security_model=none,mount_tag=xchg",
|
"virtio-rng-pci",
|
||||||
"-virtfs", f"local,path={secrets_dir},security_model=none,mount_tag=secrets",
|
"-net",
|
||||||
"-drive", f'cache=writeback,file={disk_img},format=raw,id=drive1,if=none,index=1,werror=report',
|
"nic,netdev=user.0,model=virtio",
|
||||||
"-device", "virtio-blk-pci,bootindex=1,drive=drive1,serial=root",
|
"-netdev",
|
||||||
"-device", "virtio-keyboard",
|
"user,id=user.0",
|
||||||
"-vga", "virtio",
|
"-virtfs",
|
||||||
|
"local,path=/nix/store,security_model=none,mount_tag=nix-store",
|
||||||
|
"-virtfs",
|
||||||
|
f"local,path={xchg_dir},security_model=none,mount_tag=shared",
|
||||||
|
"-virtfs",
|
||||||
|
f"local,path={xchg_dir},security_model=none,mount_tag=xchg",
|
||||||
|
"-virtfs",
|
||||||
|
f"local,path={secrets_dir},security_model=none,mount_tag=secrets",
|
||||||
|
"-drive",
|
||||||
|
f"cache=writeback,file={disk_img},format=raw,id=drive1,if=none,index=1,werror=report",
|
||||||
|
"-device",
|
||||||
|
"virtio-blk-pci,bootindex=1,drive=drive1,serial=root",
|
||||||
|
"-device",
|
||||||
|
"virtio-keyboard",
|
||||||
|
"-vga",
|
||||||
|
"virtio",
|
||||||
"-usb",
|
"-usb",
|
||||||
"-device", "usb-tablet,bus=usb-bus.0",
|
"-device",
|
||||||
"-kernel", f'{vm_config["toplevel"]}/kernel',
|
"usb-tablet,bus=usb-bus.0",
|
||||||
"-initrd", vm_config["initrd"],
|
"-kernel",
|
||||||
"-append", " ".join(cmdline),
|
f'{vm_config["toplevel"]}/kernel',
|
||||||
# fmt: on
|
"-initrd",
|
||||||
|
vm_config["initrd"],
|
||||||
|
"-append",
|
||||||
|
" ".join(cmdline),
|
||||||
]
|
]
|
||||||
if not self.vm.graphics:
|
if not self.vm.graphics:
|
||||||
qemu_command.append("-nographic")
|
qemu_command.append("-nographic")
|
||||||
@@ -179,15 +187,17 @@ class BuildVmTask(BaseTask):
|
|||||||
cmd.run(nix_shell(["qemu"], qemu_command))
|
cmd.run(nix_shell(["qemu"], qemu_command))
|
||||||
|
|
||||||
|
|
||||||
def create_vm(vm: VmConfig) -> BuildVmTask:
|
def create_vm(vm: VmConfig, nix_options: list[str] = []) -> BuildVmTask:
|
||||||
return create_task(BuildVmTask, vm)
|
return create_task(BuildVmTask, vm, nix_options)
|
||||||
|
|
||||||
|
|
||||||
def create_command(args: argparse.Namespace) -> None:
|
def create_command(args: argparse.Namespace) -> None:
|
||||||
clan_dir = specific_flake_dir(args.flake)
|
flake_url = args.flake
|
||||||
vm = asyncio.run(inspect_vm(flake_url=clan_dir, flake_attr=args.machine))
|
if not is_flake_url(args.flake):
|
||||||
|
flake_url = specific_flake_dir(args.flake)
|
||||||
|
vm = asyncio.run(inspect_vm(flake_url=flake_url, flake_attr=args.machine))
|
||||||
|
|
||||||
task = create_vm(vm)
|
task = create_vm(vm, args.option)
|
||||||
for line in task.log_lines():
|
for line in task.log_lines():
|
||||||
print(line, end="")
|
print(line, end="")
|
||||||
|
|
||||||
|
|||||||
@@ -70,6 +70,10 @@ class FlakeAction(BaseModel):
|
|||||||
uri: str
|
uri: str
|
||||||
|
|
||||||
|
|
||||||
|
class FlakeListResponse(BaseModel):
|
||||||
|
flakes: list[str]
|
||||||
|
|
||||||
|
|
||||||
class FlakeCreateResponse(BaseModel):
|
class FlakeCreateResponse(BaseModel):
|
||||||
cmd_out: Dict[str, CmdOut]
|
cmd_out: Dict[str, CmdOut]
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from ..errors import ClanError
|
|||||||
from .assets import asset_path
|
from .assets import asset_path
|
||||||
from .error_handlers import clan_error_handler
|
from .error_handlers import clan_error_handler
|
||||||
from .routers import clan_modules, flake, health, machines, root, vms
|
from .routers import clan_modules, flake, health, machines, root, vms
|
||||||
|
from .tags import tags_metadata
|
||||||
|
|
||||||
origins = [
|
origins = [
|
||||||
"http://localhost:3000",
|
"http://localhost:3000",
|
||||||
@@ -39,6 +40,9 @@ def setup_app() -> FastAPI:
|
|||||||
|
|
||||||
app.mount("/static", StaticFiles(directory=asset_path()), name="static")
|
app.mount("/static", StaticFiles(directory=asset_path()), name="static")
|
||||||
|
|
||||||
|
# Add tag descriptions to the OpenAPI schema
|
||||||
|
app.openapi_tags = tags_metadata
|
||||||
|
|
||||||
for route in app.routes:
|
for route in app.routes:
|
||||||
if isinstance(route, APIRoute):
|
if isinstance(route, APIRoute):
|
||||||
route.operation_id = route.name # in this case, 'read_items'
|
route.operation_id = route.name # in this case, 'read_items'
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ from ..errors import ClanError
|
|||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def clan_error_handler(request: Request, exc: ClanError) -> JSONResponse:
|
def clan_error_handler(request: Request, exc: Exception) -> JSONResponse:
|
||||||
|
assert isinstance(exc, ClanError)
|
||||||
log.error("ClanError: %s", exc)
|
log.error("ClanError: %s", exc)
|
||||||
detail = [
|
detail = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -9,12 +9,13 @@ from clan_cli.types import FlakeName
|
|||||||
from ..api_outputs import (
|
from ..api_outputs import (
|
||||||
ClanModulesResponse,
|
ClanModulesResponse,
|
||||||
)
|
)
|
||||||
|
from ..tags import Tags
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
@router.get("/api/{flake_name}/clan_modules")
|
@router.get("/api/{flake_name}/clan_modules", tags=[Tags.modules])
|
||||||
async def list_clan_modules(flake_name: FlakeName) -> ClanModulesResponse:
|
async def list_clan_modules(flake_name: FlakeName) -> ClanModulesResponse:
|
||||||
module_names, error = get_clan_module_names(flake_name)
|
module_names, error = get_clan_module_names(flake_name)
|
||||||
if error is not None:
|
if error is not None:
|
||||||
|
|||||||
@@ -13,12 +13,14 @@ from clan_cli.webui.api_outputs import (
|
|||||||
FlakeAction,
|
FlakeAction,
|
||||||
FlakeAttrResponse,
|
FlakeAttrResponse,
|
||||||
FlakeCreateResponse,
|
FlakeCreateResponse,
|
||||||
|
FlakeListResponse,
|
||||||
FlakeResponse,
|
FlakeResponse,
|
||||||
)
|
)
|
||||||
|
|
||||||
from ...async_cmd import run
|
from ...async_cmd import run
|
||||||
from ...flakes import create
|
from ...flakes import create, list_flakes
|
||||||
from ...nix import nix_command, nix_flake_show
|
from ...nix import nix_command, nix_flake_show
|
||||||
|
from ..tags import Tags
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
@@ -45,13 +47,13 @@ async def get_attrs(url: AnyUrl | Path) -> list[str]:
|
|||||||
|
|
||||||
|
|
||||||
# TODO: Check for directory traversal
|
# TODO: Check for directory traversal
|
||||||
@router.get("/api/flake/attrs")
|
@router.get("/api/flake/attrs", tags=[Tags.flake])
|
||||||
async def inspect_flake_attrs(url: AnyUrl | Path) -> FlakeAttrResponse:
|
async def inspect_flake_attrs(url: AnyUrl | Path) -> FlakeAttrResponse:
|
||||||
return FlakeAttrResponse(flake_attrs=await get_attrs(url))
|
return FlakeAttrResponse(flake_attrs=await get_attrs(url))
|
||||||
|
|
||||||
|
|
||||||
# TODO: Check for directory traversal
|
# TODO: Check for directory traversal
|
||||||
@router.get("/api/flake")
|
@router.get("/api/flake/inspect", tags=[Tags.flake])
|
||||||
async def inspect_flake(
|
async def inspect_flake(
|
||||||
url: AnyUrl | Path,
|
url: AnyUrl | Path,
|
||||||
) -> FlakeResponse:
|
) -> FlakeResponse:
|
||||||
@@ -76,7 +78,15 @@ async def inspect_flake(
|
|||||||
return FlakeResponse(content=content, actions=actions)
|
return FlakeResponse(content=content, actions=actions)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/api/flake/create", status_code=status.HTTP_201_CREATED)
|
@router.get("/api/flake/list", tags=[Tags.flake])
|
||||||
|
async def list_all_flakes() -> FlakeListResponse:
|
||||||
|
flakes = list_flakes.list_flakes()
|
||||||
|
return FlakeListResponse(flakes=flakes)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post(
|
||||||
|
"/api/flake/create", tags=[Tags.flake], status_code=status.HTTP_201_CREATED
|
||||||
|
)
|
||||||
async def create_flake(
|
async def create_flake(
|
||||||
args: Annotated[FlakeCreateInput, Body()],
|
args: Annotated[FlakeCreateInput, Body()],
|
||||||
) -> FlakeCreateResponse:
|
) -> FlakeCreateResponse:
|
||||||
|
|||||||
@@ -23,12 +23,13 @@ from ..api_outputs import (
|
|||||||
Status,
|
Status,
|
||||||
VerifyMachineResponse,
|
VerifyMachineResponse,
|
||||||
)
|
)
|
||||||
|
from ..tags import Tags
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
@router.get("/api/{flake_name}/machines")
|
@router.get("/api/{flake_name}/machines", tags=[Tags.machine])
|
||||||
async def list_machines(flake_name: FlakeName) -> MachinesResponse:
|
async def list_machines(flake_name: FlakeName) -> MachinesResponse:
|
||||||
machines = []
|
machines = []
|
||||||
for m in _list_machines(flake_name):
|
for m in _list_machines(flake_name):
|
||||||
@@ -37,7 +38,7 @@ async def list_machines(flake_name: FlakeName) -> MachinesResponse:
|
|||||||
return MachinesResponse(machines=machines)
|
return MachinesResponse(machines=machines)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/api/{flake_name}/machines", status_code=201)
|
@router.post("/api/{flake_name}/machines", tags=[Tags.machine], status_code=201)
|
||||||
async def create_machine(
|
async def create_machine(
|
||||||
flake_name: FlakeName, machine: Annotated[MachineCreate, Body()]
|
flake_name: FlakeName, machine: Annotated[MachineCreate, Body()]
|
||||||
) -> MachineResponse:
|
) -> MachineResponse:
|
||||||
@@ -45,19 +46,19 @@ async def create_machine(
|
|||||||
return MachineResponse(machine=Machine(name=machine.name, status=Status.UNKNOWN))
|
return MachineResponse(machine=Machine(name=machine.name, status=Status.UNKNOWN))
|
||||||
|
|
||||||
|
|
||||||
@router.get("/api/{flake_name}/machines/{name}")
|
@router.get("/api/{flake_name}/machines/{name}", tags=[Tags.machine])
|
||||||
async def get_machine(flake_name: FlakeName, name: str) -> MachineResponse:
|
async def get_machine(flake_name: FlakeName, name: str) -> MachineResponse:
|
||||||
log.error("TODO")
|
log.error("TODO")
|
||||||
return MachineResponse(machine=Machine(name=name, status=Status.UNKNOWN))
|
return MachineResponse(machine=Machine(name=name, status=Status.UNKNOWN))
|
||||||
|
|
||||||
|
|
||||||
@router.get("/api/{flake_name}/machines/{name}/config")
|
@router.get("/api/{flake_name}/machines/{name}/config", tags=[Tags.machine])
|
||||||
async def get_machine_config(flake_name: FlakeName, name: str) -> ConfigResponse:
|
async def get_machine_config(flake_name: FlakeName, name: str) -> ConfigResponse:
|
||||||
config = config_for_machine(flake_name, name)
|
config = config_for_machine(flake_name, name)
|
||||||
return ConfigResponse(config=config)
|
return ConfigResponse(config=config)
|
||||||
|
|
||||||
|
|
||||||
@router.put("/api/{flake_name}/machines/{name}/config")
|
@router.put("/api/{flake_name}/machines/{name}/config", tags=[Tags.machine])
|
||||||
async def set_machine_config(
|
async def set_machine_config(
|
||||||
flake_name: FlakeName, name: str, config: Annotated[dict, Body()]
|
flake_name: FlakeName, name: str, config: Annotated[dict, Body()]
|
||||||
) -> ConfigResponse:
|
) -> ConfigResponse:
|
||||||
@@ -65,13 +66,13 @@ async def set_machine_config(
|
|||||||
return ConfigResponse(config=config)
|
return ConfigResponse(config=config)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/api/{flake_name}/machines/{name}/schema")
|
@router.get("/api/{flake_name}/machines/{name}/schema", tags=[Tags.machine])
|
||||||
async def get_machine_schema(flake_name: FlakeName, name: str) -> SchemaResponse:
|
async def get_machine_schema(flake_name: FlakeName, name: str) -> SchemaResponse:
|
||||||
schema = schema_for_machine(flake_name, name)
|
schema = schema_for_machine(flake_name, name)
|
||||||
return SchemaResponse(schema=schema)
|
return SchemaResponse(schema=schema)
|
||||||
|
|
||||||
|
|
||||||
@router.put("/api/{flake_name}/machines/{name}/schema")
|
@router.put("/api/{flake_name}/machines/{name}/schema", tags=[Tags.machine])
|
||||||
async def set_machine_schema(
|
async def set_machine_schema(
|
||||||
flake_name: FlakeName, name: str, config: Annotated[dict, Body()]
|
flake_name: FlakeName, name: str, config: Annotated[dict, Body()]
|
||||||
) -> SchemaResponse:
|
) -> SchemaResponse:
|
||||||
@@ -79,7 +80,7 @@ async def set_machine_schema(
|
|||||||
return SchemaResponse(schema=schema)
|
return SchemaResponse(schema=schema)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/api/{flake_name}/machines/{name}/verify")
|
@router.get("/api/{flake_name}/machines/{name}/verify", tags=[Tags.machine])
|
||||||
async def get_verify_machine_config(
|
async def get_verify_machine_config(
|
||||||
flake_name: FlakeName, name: str
|
flake_name: FlakeName, name: str
|
||||||
) -> VerifyMachineResponse:
|
) -> VerifyMachineResponse:
|
||||||
@@ -88,7 +89,7 @@ async def get_verify_machine_config(
|
|||||||
return VerifyMachineResponse(success=success, error=error)
|
return VerifyMachineResponse(success=success, error=error)
|
||||||
|
|
||||||
|
|
||||||
@router.put("/api/{flake_name}/machines/{name}/verify")
|
@router.put("/api/{flake_name}/machines/{name}/verify", tags=[Tags.machine])
|
||||||
async def put_verify_machine_config(
|
async def put_verify_machine_config(
|
||||||
flake_name: FlakeName,
|
flake_name: FlakeName,
|
||||||
name: str,
|
name: str,
|
||||||
|
|||||||
@@ -6,13 +6,14 @@ from pathlib import Path
|
|||||||
from fastapi import APIRouter, Response
|
from fastapi import APIRouter, Response
|
||||||
|
|
||||||
from ..assets import asset_path
|
from ..assets import asset_path
|
||||||
|
from ..tags import Tags
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{path_name:path}")
|
@router.get("/{path_name:path}", tags=[Tags.root])
|
||||||
async def root(path_name: str) -> Response:
|
async def root(path_name: str) -> Response:
|
||||||
if path_name == "":
|
if path_name == "":
|
||||||
path_name = "index.html"
|
path_name = "index.html"
|
||||||
|
|||||||
@@ -18,13 +18,14 @@ from ..api_outputs import (
|
|||||||
VmInspectResponse,
|
VmInspectResponse,
|
||||||
VmStatusResponse,
|
VmStatusResponse,
|
||||||
)
|
)
|
||||||
|
from ..tags import Tags
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
# TODO: Check for directory traversal
|
# TODO: Check for directory traversal
|
||||||
@router.post("/api/vms/inspect")
|
@router.post("/api/vms/inspect", tags=[Tags.vm])
|
||||||
async def inspect_vm(
|
async def inspect_vm(
|
||||||
flake_url: Annotated[AnyUrl | Path, Body()], flake_attr: Annotated[str, Body()]
|
flake_url: Annotated[AnyUrl | Path, Body()], flake_attr: Annotated[str, Body()]
|
||||||
) -> VmInspectResponse:
|
) -> VmInspectResponse:
|
||||||
@@ -32,7 +33,7 @@ async def inspect_vm(
|
|||||||
return VmInspectResponse(config=config)
|
return VmInspectResponse(config=config)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/api/vms/{uuid}/status")
|
@router.get("/api/vms/{uuid}/status", tags=[Tags.vm])
|
||||||
async def get_vm_status(uuid: UUID) -> VmStatusResponse:
|
async def get_vm_status(uuid: UUID) -> VmStatusResponse:
|
||||||
task = get_task(uuid)
|
task = get_task(uuid)
|
||||||
log.debug(msg=f"error: {task.error}, task.status: {task.status}")
|
log.debug(msg=f"error: {task.error}, task.status: {task.status}")
|
||||||
@@ -40,7 +41,7 @@ async def get_vm_status(uuid: UUID) -> VmStatusResponse:
|
|||||||
return VmStatusResponse(status=task.status, error=error)
|
return VmStatusResponse(status=task.status, error=error)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/api/vms/{uuid}/logs")
|
@router.get("/api/vms/{uuid}/logs", tags=[Tags.vm])
|
||||||
async def get_vm_logs(uuid: UUID) -> StreamingResponse:
|
async def get_vm_logs(uuid: UUID) -> StreamingResponse:
|
||||||
# Generator function that yields log lines as they are available
|
# Generator function that yields log lines as they are available
|
||||||
def stream_logs() -> Iterator[str]:
|
def stream_logs() -> Iterator[str]:
|
||||||
@@ -55,7 +56,7 @@ async def get_vm_logs(uuid: UUID) -> StreamingResponse:
|
|||||||
|
|
||||||
|
|
||||||
# TODO: Check for directory traversal
|
# TODO: Check for directory traversal
|
||||||
@router.post("/api/vms/create")
|
@router.post("/api/vms/create", tags=[Tags.vm])
|
||||||
async def create_vm(vm: Annotated[VmConfig, Body()]) -> VmCreateResponse:
|
async def create_vm(vm: Annotated[VmConfig, Body()]) -> VmCreateResponse:
|
||||||
flake_attrs = await get_attrs(vm.flake_url)
|
flake_attrs = await get_attrs(vm.flake_url)
|
||||||
if vm.flake_attr not in flake_attrs:
|
if vm.flake_attr not in flake_attrs:
|
||||||
|
|||||||
41
pkgs/clan-cli/clan_cli/webui/tags.py
Normal file
41
pkgs/clan-cli/clan_cli/webui/tags.py
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
from enum import Enum
|
||||||
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
|
|
||||||
|
class Tags(Enum):
|
||||||
|
flake = "flake"
|
||||||
|
machine = "machine"
|
||||||
|
vm = "vm"
|
||||||
|
modules = "modules"
|
||||||
|
root = "root"
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return self.value
|
||||||
|
|
||||||
|
|
||||||
|
tags_metadata: List[Dict[str, Any]] = [
|
||||||
|
{
|
||||||
|
"name": str(Tags.flake),
|
||||||
|
"description": "Operations on a flake.",
|
||||||
|
"externalDocs": {
|
||||||
|
"description": "What is a flake?",
|
||||||
|
"url": "https://www.tweag.io/blog/2020-05-25-flakes/",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": str(Tags.machine),
|
||||||
|
"description": "Manage physical machines. Instances of a flake",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": str(Tags.vm),
|
||||||
|
"description": "Manage virtual machines. Instances of a flake",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": str(Tags.modules),
|
||||||
|
"description": "Manage cLAN modules of a flake",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": str(Tags.root),
|
||||||
|
"description": "This serves as the frontend delivery",
|
||||||
|
},
|
||||||
|
]
|
||||||
@@ -58,25 +58,3 @@ line-length = 88
|
|||||||
|
|
||||||
select = [ "E", "F", "I", "N"]
|
select = [ "E", "F", "I", "N"]
|
||||||
ignore = [ "E501" ]
|
ignore = [ "E501" ]
|
||||||
|
|
||||||
[tool.black]
|
|
||||||
line-length = 88
|
|
||||||
target-version = [ "py310" ]
|
|
||||||
include = "\\.pyi?$"
|
|
||||||
exclude = '''
|
|
||||||
/(
|
|
||||||
\.git
|
|
||||||
| \.hg
|
|
||||||
| \.mypy_cache
|
|
||||||
| \.tox
|
|
||||||
| \.venv
|
|
||||||
| _build
|
|
||||||
| buck-out
|
|
||||||
| build
|
|
||||||
| dist
|
|
||||||
# The following are specific to Black, you probably don't want those.
|
|
||||||
| blib2to3
|
|
||||||
| tests/data
|
|
||||||
| profiling
|
|
||||||
)/
|
|
||||||
'''
|
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ from pathlib import Path
|
|||||||
from typing import Iterator, NamedTuple
|
from typing import Iterator, NamedTuple
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from pydantic import AnyUrl
|
||||||
|
from pydantic.tools import parse_obj_as
|
||||||
from root import CLAN_CORE
|
from root import CLAN_CORE
|
||||||
|
|
||||||
from clan_cli.dirs import nixpkgs_source
|
from clan_cli.dirs import nixpkgs_source
|
||||||
@@ -117,6 +119,16 @@ def test_flake_with_core(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def test_democlan_url(
|
||||||
|
monkeypatch: pytest.MonkeyPatch, temporary_home: Path
|
||||||
|
) -> Iterator[AnyUrl]:
|
||||||
|
yield parse_obj_as(
|
||||||
|
AnyUrl,
|
||||||
|
"https://git.clan.lol/clan/democlan/archive/main.tar.gz",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def test_flake_with_core_and_pass(
|
def test_flake_with_core_and_pass(
|
||||||
monkeypatch: pytest.MonkeyPatch, temporary_home: Path
|
monkeypatch: pytest.MonkeyPatch, temporary_home: Path
|
||||||
|
|||||||
@@ -8,6 +8,15 @@ from fixtures_flakes import FlakeForTest
|
|||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.impure
|
||||||
|
def test_list_flakes(api: TestClient, test_flake_with_core: FlakeForTest) -> None:
|
||||||
|
response = api.get("/api/flake/list")
|
||||||
|
assert response.status_code == 200, "Failed to list flakes"
|
||||||
|
data = response.json()
|
||||||
|
print("Data: ", data)
|
||||||
|
assert data.get("flakes") == ["test_flake_with_core"]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.impure
|
@pytest.mark.impure
|
||||||
def test_inspect_ok(api: TestClient, test_flake_with_core: FlakeForTest) -> None:
|
def test_inspect_ok(api: TestClient, test_flake_with_core: FlakeForTest) -> None:
|
||||||
params = {"url": str(test_flake_with_core.path)}
|
params = {"url": str(test_flake_with_core.path)}
|
||||||
@@ -38,7 +47,7 @@ def test_inspect_err(api: TestClient) -> None:
|
|||||||
def test_inspect_flake(api: TestClient, test_flake_with_core: FlakeForTest) -> None:
|
def test_inspect_flake(api: TestClient, test_flake_with_core: FlakeForTest) -> None:
|
||||||
params = {"url": str(test_flake_with_core.path)}
|
params = {"url": str(test_flake_with_core.path)}
|
||||||
response = api.get(
|
response = api.get(
|
||||||
"/api/flake",
|
"/api/flake/inspect",
|
||||||
params=params,
|
params=params,
|
||||||
)
|
)
|
||||||
assert response.status_code == 200, "Failed to inspect vm"
|
assert response.status_code == 200, "Failed to inspect vm"
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from api import TestClient
|
|||||||
from cli import Cli
|
from cli import Cli
|
||||||
from fixtures_flakes import FlakeForTest, create_flake
|
from fixtures_flakes import FlakeForTest, create_flake
|
||||||
from httpx import SyncByteStream
|
from httpx import SyncByteStream
|
||||||
|
from pydantic import AnyUrl
|
||||||
from root import CLAN_CORE
|
from root import CLAN_CORE
|
||||||
|
|
||||||
from clan_cli.types import FlakeName
|
from clan_cli.types import FlakeName
|
||||||
@@ -42,7 +43,7 @@ def remote_flake_with_vm_without_secrets(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def generic_create_vm_test(api: TestClient, flake: Path, vm: str) -> None:
|
def generic_create_vm_test(api: TestClient, flake: Path | AnyUrl, vm: str) -> None:
|
||||||
print(f"flake_url: {flake} ")
|
print(f"flake_url: {flake} ")
|
||||||
response = api.post(
|
response = api.post(
|
||||||
"/api/vms/create",
|
"/api/vms/create",
|
||||||
@@ -113,3 +114,18 @@ def test_create_remote(
|
|||||||
generic_create_vm_test(
|
generic_create_vm_test(
|
||||||
api, remote_flake_with_vm_without_secrets.path, "vm_without_secrets"
|
api, remote_flake_with_vm_without_secrets.path, "vm_without_secrets"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: We need a test that creates the same VM twice, and checks that the second time it fails
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Democlan needs a machine called testVM, which is headless and gets executed by this test below
|
||||||
|
# pytest -n0 -s tests/test_vms_api_create.py::test_create_from_democlan
|
||||||
|
# @pytest.mark.skipif(not os.path.exists("/dev/kvm"), reason="Requires KVM")
|
||||||
|
# @pytest.mark.impure
|
||||||
|
# def test_create_from_democlan(
|
||||||
|
# api: TestClient,
|
||||||
|
# test_democlan_url: AnyUrl) -> None:
|
||||||
|
# generic_create_vm_test(
|
||||||
|
# api, test_democlan_url, "defaultVM"
|
||||||
|
# )
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{ fetchzip }:
|
{ fetchzip }:
|
||||||
fetchzip {
|
fetchzip {
|
||||||
url = "https://git.clan.lol/api/packages/clan/generic/ui/01mhnbcfx4b5as3fz2mx45izf6k3x2idahf0p8vxvhnh9d6h8sdj/assets.tar.gz";
|
url = "https://git.clan.lol/api/packages/clan/generic/ui/0vfv15ff7ja9j6a3hj4365gyzcjy0f4zwy38igdyr0smis7a0qj4/assets.tar.gz";
|
||||||
sha256 = "01mhnbcfx4b5as3fz2mx45izf6k3x2idahf0p8vxvhnh9d6h8sdj";
|
sha256 = "0vfv15ff7ja9j6a3hj4365gyzcjy0f4zwy38igdyr0smis7a0qj4";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ pkgs.mkShell {
|
|||||||
|
|
||||||
|
|
||||||
# re-generate the api code
|
# re-generate the api code
|
||||||
rm -rf api openapi.json
|
rm -rf src/api openapi.json
|
||||||
cp ${clanPkgs.clan-openapi}/openapi.json .
|
cp ${clanPkgs.clan-openapi}/openapi.json .
|
||||||
orval
|
orval
|
||||||
'';
|
'';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { useGetMachineSchema } from "@/api/default/default";
|
import { useGetMachineSchema } from "@/api/machine/machine";
|
||||||
import { Check, Error } from "@mui/icons-material";
|
import { Check, Error } from "@mui/icons-material";
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useListMachines } from "@/api/default/default";
|
import { useListMachines } from "@/api/machine/machine";
|
||||||
import { Machine, MachinesResponse } from "@/api/model";
|
import { Machine, MachinesResponse } from "@/api/model";
|
||||||
import { AxiosError, AxiosResponse } from "axios";
|
import { AxiosError, AxiosResponse } from "axios";
|
||||||
import React, {
|
import React, {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { inspectVm } from "@/api/default/default";
|
import { inspectVm } from "@/api/vm/vm";
|
||||||
import { HTTPValidationError, VmConfig } from "@/api/model";
|
import { HTTPValidationError, VmConfig } from "@/api/model";
|
||||||
import { AxiosError } from "axios";
|
import { AxiosError } from "axios";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ import {
|
|||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { Controller, SubmitHandler, UseFormReturn } from "react-hook-form";
|
import { Controller, SubmitHandler, UseFormReturn } from "react-hook-form";
|
||||||
import { FlakeBadge } from "../flakeBadge/flakeBadge";
|
import { FlakeBadge } from "../flakeBadge/flakeBadge";
|
||||||
import { createVm, useInspectFlakeAttrs } from "@/api/default/default";
|
import { createVm } from "@/api/vm/vm";
|
||||||
|
import { useInspectFlakeAttrs } from "@/api/flake/flake";
|
||||||
import { VmConfig } from "@/api/model";
|
import { VmConfig } from "@/api/model";
|
||||||
import { Dispatch, SetStateAction, useEffect, useState } from "react";
|
import { Dispatch, SetStateAction, useEffect, useState } from "react";
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { Typography, Button } from "@mui/material";
|
|||||||
import { ConfirmVM } from "./confirmVM";
|
import { ConfirmVM } from "./confirmVM";
|
||||||
import { Log } from "./log";
|
import { Log } from "./log";
|
||||||
import GppMaybeIcon from "@mui/icons-material/GppMaybe";
|
import GppMaybeIcon from "@mui/icons-material/GppMaybe";
|
||||||
import { useInspectFlake } from "@/api/default/default";
|
import { useInspectFlake } from "@/api/flake/flake";
|
||||||
|
|
||||||
interface ConfirmProps {
|
interface ConfirmProps {
|
||||||
flakeUrl: string;
|
flakeUrl: string;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { useGetVmLogs } from "@/api/default/default";
|
import { useGetVmLogs } from "@/api/vm/vm";
|
||||||
import { Log } from "./log";
|
import { Log } from "./log";
|
||||||
import { LoadingOverlay } from "./loadingOverlay";
|
import { LoadingOverlay } from "./loadingOverlay";
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
[tool.mypy]
|
[tool.mypy]
|
||||||
python_version = "3.10"
|
python_version = "3.10"
|
||||||
|
pretty = true
|
||||||
warn_redundant_casts = true
|
warn_redundant_casts = true
|
||||||
disallow_untyped_calls = true
|
disallow_untyped_calls = true
|
||||||
disallow_untyped_defs = true
|
disallow_untyped_defs = true
|
||||||
@@ -11,25 +12,3 @@ line-length = 88
|
|||||||
|
|
||||||
select = [ "E", "F", "I", "U", "N"]
|
select = [ "E", "F", "I", "U", "N"]
|
||||||
ignore = [ "E501" ]
|
ignore = [ "E501" ]
|
||||||
|
|
||||||
[tool.black]
|
|
||||||
line-length = 88
|
|
||||||
target-version = [ "py310" ]
|
|
||||||
include = "\\.pyi?$"
|
|
||||||
exclude = '''
|
|
||||||
/(
|
|
||||||
\.git
|
|
||||||
| \.hg
|
|
||||||
| \.mypy_cache
|
|
||||||
| \.tox
|
|
||||||
| \.venv
|
|
||||||
| _build
|
|
||||||
| buck-out
|
|
||||||
| build
|
|
||||||
| dist
|
|
||||||
# The following are specific to Black, you probably don't want those.
|
|
||||||
| blib2to3
|
|
||||||
| tests/data
|
|
||||||
| profiling
|
|
||||||
)/
|
|
||||||
'''
|
|
||||||
|
|||||||
Reference in New Issue
Block a user