Compare commits
	
		
			16 Commits
		
	
	
		
			58157e45fb
			...
			d47121ceb6
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| d47121ceb6 | |||
| adbf8eba80 | |||
| f170c09a02 | |||
| eb8cac48f2 | |||
| 1d69e04e22 | |||
| fd8dbd9ce7 | |||
| af22741ade | |||
| 13a00e2318 | |||
| 65ee3231b2 | |||
| 9630cd5f95 | |||
| 25eeab6098 | |||
| 420653bcb6 | |||
| ddc5d24857 | |||
| a25c53d709 | |||
| 41d9bd104f | |||
| 51d50ff496 | 
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@ -1,2 +1,4 @@
 | 
			
		||||
# SPDX-License-Identifier: Apache-2.0
 | 
			
		||||
 | 
			
		||||
/target
 | 
			
		||||
tmp
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										15
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										15
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							@ -1,5 +1,6 @@
 | 
			
		||||
# This file is automatically @generated by Cargo.
 | 
			
		||||
# It is not intended for manual editing.
 | 
			
		||||
# SPDX-License-Identifier: Apache-2.0
 | 
			
		||||
version = 4
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
@ -1131,6 +1132,7 @@ dependencies = [
 | 
			
		||||
 "reqwest",
 | 
			
		||||
 "rustls",
 | 
			
		||||
 "serde",
 | 
			
		||||
 "serde_default_utils",
 | 
			
		||||
 "serde_json",
 | 
			
		||||
 "serde_yaml",
 | 
			
		||||
 "shadow-rs",
 | 
			
		||||
@ -1182,7 +1184,7 @@ dependencies = [
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "detee-shared"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
source = "git+ssh://git@gitea.detee.cloud/testnet/proto.git?branch=main#b5289f1f5ba3ddae2ee066d6deb073ce92436b71"
 | 
			
		||||
source = "git+ssh://git@gitea.detee.cloud/testnet/proto.git?branch=credits_app#01e93d3a2e4502c0e8e72026e8a1c55810961815"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "bincode",
 | 
			
		||||
 "prost",
 | 
			
		||||
@ -3061,7 +3063,7 @@ dependencies = [
 | 
			
		||||
 "errno",
 | 
			
		||||
 "libc",
 | 
			
		||||
 "linux-raw-sys 0.9.4",
 | 
			
		||||
 "windows-sys 0.52.0",
 | 
			
		||||
 "windows-sys 0.59.0",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
@ -3238,6 +3240,15 @@ dependencies = [
 | 
			
		||||
 "serde_derive",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "serde_default_utils"
 | 
			
		||||
version = "0.3.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "61460b1489ce48857e7eee87aa4fde5cbe4e9efc29b6f07a35df9ec82379b74b"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "paste",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "serde_derive"
 | 
			
		||||
version = "1.0.216"
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,5 @@
 | 
			
		||||
# SPDX-License-Identifier: Apache-2.0
 | 
			
		||||
 | 
			
		||||
[package]
 | 
			
		||||
name = "detee-cli"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
@ -33,8 +35,9 @@ openssl = { version = "0.10.71", features = ["vendored"] }
 | 
			
		||||
tokio-retry = "0.3.0"
 | 
			
		||||
detee-sgx = { git = "ssh://git@gitea.detee.cloud/testnet/detee-sgx.git", branch = "hratls", features=["hratls", "qvl"] }
 | 
			
		||||
shadow-rs = { version = "1.1.1", features = ["metadata"] }
 | 
			
		||||
serde_default_utils = "0.3.1"
 | 
			
		||||
 | 
			
		||||
detee-shared = { git = "ssh://git@gitea.detee.cloud/testnet/proto.git", branch = "main" }
 | 
			
		||||
detee-shared = { git = "ssh://git@gitea.detee.cloud/testnet/proto.git", branch = "credits_app" }
 | 
			
		||||
# detee-shared = { path = "../detee-shared" }
 | 
			
		||||
 | 
			
		||||
[build-dependencies]
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										474
									
								
								LICENSE
									
									
									
									
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										474
									
								
								LICENSE
									
									
									
									
									
								
							@ -1,338 +1,202 @@
 | 
			
		||||
                    GNU GENERAL PUBLIC LICENSE
 | 
			
		||||
                       Version 2, June 1991
 | 
			
		||||
 | 
			
		||||
 Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
 | 
			
		||||
 <https://fsf.org/>
 | 
			
		||||
 Everyone is permitted to copy and distribute verbatim copies
 | 
			
		||||
 of this license document, but changing it is not allowed.
 | 
			
		||||
                                 Apache License
 | 
			
		||||
                           Version 2.0, January 2004
 | 
			
		||||
                        http://www.apache.org/licenses/
 | 
			
		||||
 | 
			
		||||
                            Preamble
 | 
			
		||||
   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
 | 
			
		||||
 | 
			
		||||
  The licenses for most software are designed to take away your
 | 
			
		||||
freedom to share and change it.  By contrast, the GNU General Public
 | 
			
		||||
License is intended to guarantee your freedom to share and change free
 | 
			
		||||
software--to make sure the software is free for all its users.  This
 | 
			
		||||
General Public License applies to most of the Free Software
 | 
			
		||||
Foundation's software and to any other program whose authors commit to
 | 
			
		||||
using it.  (Some other Free Software Foundation software is covered by
 | 
			
		||||
the GNU Lesser General Public License instead.)  You can apply it to
 | 
			
		||||
your programs, too.
 | 
			
		||||
   1. Definitions.
 | 
			
		||||
 | 
			
		||||
  When we speak of free software, we are referring to freedom, not
 | 
			
		||||
price.  Our General Public Licenses are designed to make sure that you
 | 
			
		||||
have the freedom to distribute copies of free software (and charge for
 | 
			
		||||
this service if you wish), that you receive source code or can get it
 | 
			
		||||
if you want it, that you can change the software or use pieces of it
 | 
			
		||||
in new free programs; and that you know you can do these things.
 | 
			
		||||
      "License" shall mean the terms and conditions for use, reproduction,
 | 
			
		||||
      and distribution as defined by Sections 1 through 9 of this document.
 | 
			
		||||
 | 
			
		||||
  To protect your rights, we need to make restrictions that forbid
 | 
			
		||||
anyone to deny you these rights or to ask you to surrender the rights.
 | 
			
		||||
These restrictions translate to certain responsibilities for you if you
 | 
			
		||||
distribute copies of the software, or if you modify it.
 | 
			
		||||
      "Licensor" shall mean the copyright owner or entity authorized by
 | 
			
		||||
      the copyright owner that is granting the License.
 | 
			
		||||
 | 
			
		||||
  For example, if you distribute copies of such a program, whether
 | 
			
		||||
gratis or for a fee, you must give the recipients all the rights that
 | 
			
		||||
you have.  You must make sure that they, too, receive or can get the
 | 
			
		||||
source code.  And you must show them these terms so they know their
 | 
			
		||||
rights.
 | 
			
		||||
      "Legal Entity" shall mean the union of the acting entity and all
 | 
			
		||||
      other entities that control, are controlled by, or are under common
 | 
			
		||||
      control with that entity. For the purposes of this definition,
 | 
			
		||||
      "control" means (i) the power, direct or indirect, to cause the
 | 
			
		||||
      direction or management of such entity, whether by contract or
 | 
			
		||||
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
 | 
			
		||||
      outstanding shares, or (iii) beneficial ownership of such entity.
 | 
			
		||||
 | 
			
		||||
  We protect your rights with two steps: (1) copyright the software, and
 | 
			
		||||
(2) offer you this license which gives you legal permission to copy,
 | 
			
		||||
distribute and/or modify the software.
 | 
			
		||||
      "You" (or "Your") shall mean an individual or Legal Entity
 | 
			
		||||
      exercising permissions granted by this License.
 | 
			
		||||
 | 
			
		||||
  Also, for each author's protection and ours, we want to make certain
 | 
			
		||||
that everyone understands that there is no warranty for this free
 | 
			
		||||
software.  If the software is modified by someone else and passed on, we
 | 
			
		||||
want its recipients to know that what they have is not the original, so
 | 
			
		||||
that any problems introduced by others will not reflect on the original
 | 
			
		||||
authors' reputations.
 | 
			
		||||
      "Source" form shall mean the preferred form for making modifications,
 | 
			
		||||
      including but not limited to software source code, documentation
 | 
			
		||||
      source, and configuration files.
 | 
			
		||||
 | 
			
		||||
  Finally, any free program is threatened constantly by software
 | 
			
		||||
patents.  We wish to avoid the danger that redistributors of a free
 | 
			
		||||
program will individually obtain patent licenses, in effect making the
 | 
			
		||||
program proprietary.  To prevent this, we have made it clear that any
 | 
			
		||||
patent must be licensed for everyone's free use or not licensed at all.
 | 
			
		||||
      "Object" form shall mean any form resulting from mechanical
 | 
			
		||||
      transformation or translation of a Source form, including but
 | 
			
		||||
      not limited to compiled object code, generated documentation,
 | 
			
		||||
      and conversions to other media types.
 | 
			
		||||
 | 
			
		||||
  The precise terms and conditions for copying, distribution and
 | 
			
		||||
modification follow.
 | 
			
		||||
      "Work" shall mean the work of authorship, whether in Source or
 | 
			
		||||
      Object form, made available under the License, as indicated by a
 | 
			
		||||
      copyright notice that is included in or attached to the work
 | 
			
		||||
      (an example is provided in the Appendix below).
 | 
			
		||||
 | 
			
		||||
                    GNU GENERAL PUBLIC LICENSE
 | 
			
		||||
   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
 | 
			
		||||
      "Derivative Works" shall mean any work, whether in Source or Object
 | 
			
		||||
      form, that is based on (or derived from) the Work and for which the
 | 
			
		||||
      editorial revisions, annotations, elaborations, or other modifications
 | 
			
		||||
      represent, as a whole, an original work of authorship. For the purposes
 | 
			
		||||
      of this License, Derivative Works shall not include works that remain
 | 
			
		||||
      separable from, or merely link (or bind by name) to the interfaces of,
 | 
			
		||||
      the Work and Derivative Works thereof.
 | 
			
		||||
 | 
			
		||||
  0. This License applies to any program or other work which contains
 | 
			
		||||
a notice placed by the copyright holder saying it may be distributed
 | 
			
		||||
under the terms of this General Public License.  The "Program", below,
 | 
			
		||||
refers to any such program or work, and a "work based on the Program"
 | 
			
		||||
means either the Program or any derivative work under copyright law:
 | 
			
		||||
that is to say, a work containing the Program or a portion of it,
 | 
			
		||||
either verbatim or with modifications and/or translated into another
 | 
			
		||||
language.  (Hereinafter, translation is included without limitation in
 | 
			
		||||
the term "modification".)  Each licensee is addressed as "you".
 | 
			
		||||
      "Contribution" shall mean any work of authorship, including
 | 
			
		||||
      the original version of the Work and any modifications or additions
 | 
			
		||||
      to that Work or Derivative Works thereof, that is intentionally
 | 
			
		||||
      submitted to Licensor for inclusion in the Work by the copyright owner
 | 
			
		||||
      or by an individual or Legal Entity authorized to submit on behalf of
 | 
			
		||||
      the copyright owner. For the purposes of this definition, "submitted"
 | 
			
		||||
      means any form of electronic, verbal, or written communication sent
 | 
			
		||||
      to the Licensor or its representatives, including but not limited to
 | 
			
		||||
      communication on electronic mailing lists, source code control systems,
 | 
			
		||||
      and issue tracking systems that are managed by, or on behalf of, the
 | 
			
		||||
      Licensor for the purpose of discussing and improving the Work, but
 | 
			
		||||
      excluding communication that is conspicuously marked or otherwise
 | 
			
		||||
      designated in writing by the copyright owner as "Not a Contribution."
 | 
			
		||||
 | 
			
		||||
Activities other than copying, distribution and modification are not
 | 
			
		||||
covered by this License; they are outside its scope.  The act of
 | 
			
		||||
running the Program is not restricted, and the output from the Program
 | 
			
		||||
is covered only if its contents constitute a work based on the
 | 
			
		||||
Program (independent of having been made by running the Program).
 | 
			
		||||
Whether that is true depends on what the Program does.
 | 
			
		||||
      "Contributor" shall mean Licensor and any individual or Legal Entity
 | 
			
		||||
      on behalf of whom a Contribution has been received by Licensor and
 | 
			
		||||
      subsequently incorporated within the Work.
 | 
			
		||||
 | 
			
		||||
  1. You may copy and distribute verbatim copies of the Program's
 | 
			
		||||
source code as you receive it, in any medium, provided that you
 | 
			
		||||
conspicuously and appropriately publish on each copy an appropriate
 | 
			
		||||
copyright notice and disclaimer of warranty; keep intact all the
 | 
			
		||||
notices that refer to this License and to the absence of any warranty;
 | 
			
		||||
and give any other recipients of the Program a copy of this License
 | 
			
		||||
along with the Program.
 | 
			
		||||
   2. Grant of Copyright License. Subject to the terms and conditions of
 | 
			
		||||
      this License, each Contributor hereby grants to You a perpetual,
 | 
			
		||||
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
 | 
			
		||||
      copyright license to reproduce, prepare Derivative Works of,
 | 
			
		||||
      publicly display, publicly perform, sublicense, and distribute the
 | 
			
		||||
      Work and such Derivative Works in Source or Object form.
 | 
			
		||||
 | 
			
		||||
You may charge a fee for the physical act of transferring a copy, and
 | 
			
		||||
you may at your option offer warranty protection in exchange for a fee.
 | 
			
		||||
   3. Grant of Patent License. Subject to the terms and conditions of
 | 
			
		||||
      this License, each Contributor hereby grants to You a perpetual,
 | 
			
		||||
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
 | 
			
		||||
      (except as stated in this section) patent license to make, have made,
 | 
			
		||||
      use, offer to sell, sell, import, and otherwise transfer the Work,
 | 
			
		||||
      where such license applies only to those patent claims licensable
 | 
			
		||||
      by such Contributor that are necessarily infringed by their
 | 
			
		||||
      Contribution(s) alone or by combination of their Contribution(s)
 | 
			
		||||
      with the Work to which such Contribution(s) was submitted. If You
 | 
			
		||||
      institute patent litigation against any entity (including a
 | 
			
		||||
      cross-claim or counterclaim in a lawsuit) alleging that the Work
 | 
			
		||||
      or a Contribution incorporated within the Work constitutes direct
 | 
			
		||||
      or contributory patent infringement, then any patent licenses
 | 
			
		||||
      granted to You under this License for that Work shall terminate
 | 
			
		||||
      as of the date such litigation is filed.
 | 
			
		||||
 | 
			
		||||
  2. You may modify your copy or copies of the Program or any portion
 | 
			
		||||
of it, thus forming a work based on the Program, and copy and
 | 
			
		||||
distribute such modifications or work under the terms of Section 1
 | 
			
		||||
above, provided that you also meet all of these conditions:
 | 
			
		||||
   4. Redistribution. You may reproduce and distribute copies of the
 | 
			
		||||
      Work or Derivative Works thereof in any medium, with or without
 | 
			
		||||
      modifications, and in Source or Object form, provided that You
 | 
			
		||||
      meet the following conditions:
 | 
			
		||||
 | 
			
		||||
    a) You must cause the modified files to carry prominent notices
 | 
			
		||||
    stating that you changed the files and the date of any change.
 | 
			
		||||
      (a) You must give any other recipients of the Work or
 | 
			
		||||
          Derivative Works a copy of this License; and
 | 
			
		||||
 | 
			
		||||
    b) You must cause any work that you distribute or publish, that in
 | 
			
		||||
    whole or in part contains or is derived from the Program or any
 | 
			
		||||
    part thereof, to be licensed as a whole at no charge to all third
 | 
			
		||||
    parties under the terms of this License.
 | 
			
		||||
      (b) You must cause any modified files to carry prominent notices
 | 
			
		||||
          stating that You changed the files; and
 | 
			
		||||
 | 
			
		||||
    c) If the modified program normally reads commands interactively
 | 
			
		||||
    when run, you must cause it, when started running for such
 | 
			
		||||
    interactive use in the most ordinary way, to print or display an
 | 
			
		||||
    announcement including an appropriate copyright notice and a
 | 
			
		||||
    notice that there is no warranty (or else, saying that you provide
 | 
			
		||||
    a warranty) and that users may redistribute the program under
 | 
			
		||||
    these conditions, and telling the user how to view a copy of this
 | 
			
		||||
    License.  (Exception: if the Program itself is interactive but
 | 
			
		||||
    does not normally print such an announcement, your work based on
 | 
			
		||||
    the Program is not required to print an announcement.)
 | 
			
		||||
      (c) You must retain, in the Source form of any Derivative Works
 | 
			
		||||
          that You distribute, all copyright, patent, trademark, and
 | 
			
		||||
          attribution notices from the Source form of the Work,
 | 
			
		||||
          excluding those notices that do not pertain to any part of
 | 
			
		||||
          the Derivative Works; and
 | 
			
		||||
 | 
			
		||||
These requirements apply to the modified work as a whole.  If
 | 
			
		||||
identifiable sections of that work are not derived from the Program,
 | 
			
		||||
and can be reasonably considered independent and separate works in
 | 
			
		||||
themselves, then this License, and its terms, do not apply to those
 | 
			
		||||
sections when you distribute them as separate works.  But when you
 | 
			
		||||
distribute the same sections as part of a whole which is a work based
 | 
			
		||||
on the Program, the distribution of the whole must be on the terms of
 | 
			
		||||
this License, whose permissions for other licensees extend to the
 | 
			
		||||
entire whole, and thus to each and every part regardless of who wrote it.
 | 
			
		||||
      (d) If the Work includes a "NOTICE" text file as part of its
 | 
			
		||||
          distribution, then any Derivative Works that You distribute must
 | 
			
		||||
          include a readable copy of the attribution notices contained
 | 
			
		||||
          within such NOTICE file, excluding those notices that do not
 | 
			
		||||
          pertain to any part of the Derivative Works, in at least one
 | 
			
		||||
          of the following places: within a NOTICE text file distributed
 | 
			
		||||
          as part of the Derivative Works; within the Source form or
 | 
			
		||||
          documentation, if provided along with the Derivative Works; or,
 | 
			
		||||
          within a display generated by the Derivative Works, if and
 | 
			
		||||
          wherever such third-party notices normally appear. The contents
 | 
			
		||||
          of the NOTICE file are for informational purposes only and
 | 
			
		||||
          do not modify the License. You may add Your own attribution
 | 
			
		||||
          notices within Derivative Works that You distribute, alongside
 | 
			
		||||
          or as an addendum to the NOTICE text from the Work, provided
 | 
			
		||||
          that such additional attribution notices cannot be construed
 | 
			
		||||
          as modifying the License.
 | 
			
		||||
 | 
			
		||||
Thus, it is not the intent of this section to claim rights or contest
 | 
			
		||||
your rights to work written entirely by you; rather, the intent is to
 | 
			
		||||
exercise the right to control the distribution of derivative or
 | 
			
		||||
collective works based on the Program.
 | 
			
		||||
      You may add Your own copyright statement to Your modifications and
 | 
			
		||||
      may provide additional or different license terms and conditions
 | 
			
		||||
      for use, reproduction, or distribution of Your modifications, or
 | 
			
		||||
      for any such Derivative Works as a whole, provided Your use,
 | 
			
		||||
      reproduction, and distribution of the Work otherwise complies with
 | 
			
		||||
      the conditions stated in this License.
 | 
			
		||||
 | 
			
		||||
In addition, mere aggregation of another work not based on the Program
 | 
			
		||||
with the Program (or with a work based on the Program) on a volume of
 | 
			
		||||
a storage or distribution medium does not bring the other work under
 | 
			
		||||
the scope of this License.
 | 
			
		||||
   5. Submission of Contributions. Unless You explicitly state otherwise,
 | 
			
		||||
      any Contribution intentionally submitted for inclusion in the Work
 | 
			
		||||
      by You to the Licensor shall be under the terms and conditions of
 | 
			
		||||
      this License, without any additional terms or conditions.
 | 
			
		||||
      Notwithstanding the above, nothing herein shall supersede or modify
 | 
			
		||||
      the terms of any separate license agreement you may have executed
 | 
			
		||||
      with Licensor regarding such Contributions.
 | 
			
		||||
 | 
			
		||||
  3. You may copy and distribute the Program (or a work based on it,
 | 
			
		||||
under Section 2) in object code or executable form under the terms of
 | 
			
		||||
Sections 1 and 2 above provided that you also do one of the following:
 | 
			
		||||
   6. Trademarks. This License does not grant permission to use the trade
 | 
			
		||||
      names, trademarks, service marks, or product names of the Licensor,
 | 
			
		||||
      except as required for reasonable and customary use in describing the
 | 
			
		||||
      origin of the Work and reproducing the content of the NOTICE file.
 | 
			
		||||
 | 
			
		||||
    a) Accompany it with the complete corresponding machine-readable
 | 
			
		||||
    source code, which must be distributed under the terms of Sections
 | 
			
		||||
    1 and 2 above on a medium customarily used for software interchange; or,
 | 
			
		||||
   7. Disclaimer of Warranty. Unless required by applicable law or
 | 
			
		||||
      agreed to in writing, Licensor provides the Work (and each
 | 
			
		||||
      Contributor provides its Contributions) on an "AS IS" BASIS,
 | 
			
		||||
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 | 
			
		||||
      implied, including, without limitation, any warranties or conditions
 | 
			
		||||
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
 | 
			
		||||
      PARTICULAR PURPOSE. You are solely responsible for determining the
 | 
			
		||||
      appropriateness of using or redistributing the Work and assume any
 | 
			
		||||
      risks associated with Your exercise of permissions under this License.
 | 
			
		||||
 | 
			
		||||
    b) Accompany it with a written offer, valid for at least three
 | 
			
		||||
    years, to give any third party, for a charge no more than your
 | 
			
		||||
    cost of physically performing source distribution, a complete
 | 
			
		||||
    machine-readable copy of the corresponding source code, to be
 | 
			
		||||
    distributed under the terms of Sections 1 and 2 above on a medium
 | 
			
		||||
    customarily used for software interchange; or,
 | 
			
		||||
   8. Limitation of Liability. In no event and under no legal theory,
 | 
			
		||||
      whether in tort (including negligence), contract, or otherwise,
 | 
			
		||||
      unless required by applicable law (such as deliberate and grossly
 | 
			
		||||
      negligent acts) or agreed to in writing, shall any Contributor be
 | 
			
		||||
      liable to You for damages, including any direct, indirect, special,
 | 
			
		||||
      incidental, or consequential damages of any character arising as a
 | 
			
		||||
      result of this License or out of the use or inability to use the
 | 
			
		||||
      Work (including but not limited to damages for loss of goodwill,
 | 
			
		||||
      work stoppage, computer failure or malfunction, or any and all
 | 
			
		||||
      other commercial damages or losses), even if such Contributor
 | 
			
		||||
      has been advised of the possibility of such damages.
 | 
			
		||||
 | 
			
		||||
    c) Accompany it with the information you received as to the offer
 | 
			
		||||
    to distribute corresponding source code.  (This alternative is
 | 
			
		||||
    allowed only for noncommercial distribution and only if you
 | 
			
		||||
    received the program in object code or executable form with such
 | 
			
		||||
    an offer, in accord with Subsection b above.)
 | 
			
		||||
   9. Accepting Warranty or Additional Liability. While redistributing
 | 
			
		||||
      the Work or Derivative Works thereof, You may choose to offer,
 | 
			
		||||
      and charge a fee for, acceptance of support, warranty, indemnity,
 | 
			
		||||
      or other liability obligations and/or rights consistent with this
 | 
			
		||||
      License. However, in accepting such obligations, You may act only
 | 
			
		||||
      on Your own behalf and on Your sole responsibility, not on behalf
 | 
			
		||||
      of any other Contributor, and only if You agree to indemnify,
 | 
			
		||||
      defend, and hold each Contributor harmless for any liability
 | 
			
		||||
      incurred by, or claims asserted against, such Contributor by reason
 | 
			
		||||
      of your accepting any such warranty or additional liability.
 | 
			
		||||
 | 
			
		||||
The source code for a work means the preferred form of the work for
 | 
			
		||||
making modifications to it.  For an executable work, complete source
 | 
			
		||||
code means all the source code for all modules it contains, plus any
 | 
			
		||||
associated interface definition files, plus the scripts used to
 | 
			
		||||
control compilation and installation of the executable.  However, as a
 | 
			
		||||
special exception, the source code distributed need not include
 | 
			
		||||
anything that is normally distributed (in either source or binary
 | 
			
		||||
form) with the major components (compiler, kernel, and so on) of the
 | 
			
		||||
operating system on which the executable runs, unless that component
 | 
			
		||||
itself accompanies the executable.
 | 
			
		||||
   END OF TERMS AND CONDITIONS
 | 
			
		||||
 | 
			
		||||
If distribution of executable or object code is made by offering
 | 
			
		||||
access to copy from a designated place, then offering equivalent
 | 
			
		||||
access to copy the source code from the same place counts as
 | 
			
		||||
distribution of the source code, even though third parties are not
 | 
			
		||||
compelled to copy the source along with the object code.
 | 
			
		||||
   APPENDIX: How to apply the Apache License to your work.
 | 
			
		||||
 | 
			
		||||
  4. You may not copy, modify, sublicense, or distribute the Program
 | 
			
		||||
except as expressly provided under this License.  Any attempt
 | 
			
		||||
otherwise to copy, modify, sublicense or distribute the Program is
 | 
			
		||||
void, and will automatically terminate your rights under this License.
 | 
			
		||||
However, parties who have received copies, or rights, from you under
 | 
			
		||||
this License will not have their licenses terminated so long as such
 | 
			
		||||
parties remain in full compliance.
 | 
			
		||||
      To apply the Apache License to your work, attach the following
 | 
			
		||||
      boilerplate notice, with the fields enclosed by brackets "[]"
 | 
			
		||||
      replaced with your own identifying information. (Don't include
 | 
			
		||||
      the brackets!)  The text should be enclosed in the appropriate
 | 
			
		||||
      comment syntax for the file format. We also recommend that a
 | 
			
		||||
      file or class name and description of purpose be included on the
 | 
			
		||||
      same "printed page" as the copyright notice for easier
 | 
			
		||||
      identification within third-party archives.
 | 
			
		||||
 | 
			
		||||
  5. You are not required to accept this License, since you have not
 | 
			
		||||
signed it.  However, nothing else grants you permission to modify or
 | 
			
		||||
distribute the Program or its derivative works.  These actions are
 | 
			
		||||
prohibited by law if you do not accept this License.  Therefore, by
 | 
			
		||||
modifying or distributing the Program (or any work based on the
 | 
			
		||||
Program), you indicate your acceptance of this License to do so, and
 | 
			
		||||
all its terms and conditions for copying, distributing or modifying
 | 
			
		||||
the Program or works based on it.
 | 
			
		||||
   Copyright [yyyy] [name of copyright owner]
 | 
			
		||||
 | 
			
		||||
  6. Each time you redistribute the Program (or any work based on the
 | 
			
		||||
Program), the recipient automatically receives a license from the
 | 
			
		||||
original licensor to copy, distribute or modify the Program subject to
 | 
			
		||||
these terms and conditions.  You may not impose any further
 | 
			
		||||
restrictions on the recipients' exercise of the rights granted herein.
 | 
			
		||||
You are not responsible for enforcing compliance by third parties to
 | 
			
		||||
this License.
 | 
			
		||||
   Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
   you may not use this file except in compliance with the License.
 | 
			
		||||
   You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
  7. If, as a consequence of a court judgment or allegation of patent
 | 
			
		||||
infringement or for any other reason (not limited to patent issues),
 | 
			
		||||
conditions are imposed on you (whether by court order, agreement or
 | 
			
		||||
otherwise) that contradict the conditions of this License, they do not
 | 
			
		||||
excuse you from the conditions of this License.  If you cannot
 | 
			
		||||
distribute so as to satisfy simultaneously your obligations under this
 | 
			
		||||
License and any other pertinent obligations, then as a consequence you
 | 
			
		||||
may not distribute the Program at all.  For example, if a patent
 | 
			
		||||
license would not permit royalty-free redistribution of the Program by
 | 
			
		||||
all those who receive copies directly or indirectly through you, then
 | 
			
		||||
the only way you could satisfy both it and this License would be to
 | 
			
		||||
refrain entirely from distribution of the Program.
 | 
			
		||||
       http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
If any portion of this section is held invalid or unenforceable under
 | 
			
		||||
any particular circumstance, the balance of the section is intended to
 | 
			
		||||
apply and the section as a whole is intended to apply in other
 | 
			
		||||
circumstances.
 | 
			
		||||
 | 
			
		||||
It is not the purpose of this section to induce you to infringe any
 | 
			
		||||
patents or other property right claims or to contest validity of any
 | 
			
		||||
such claims; this section has the sole purpose of protecting the
 | 
			
		||||
integrity of the free software distribution system, which is
 | 
			
		||||
implemented by public license practices.  Many people have made
 | 
			
		||||
generous contributions to the wide range of software distributed
 | 
			
		||||
through that system in reliance on consistent application of that
 | 
			
		||||
system; it is up to the author/donor to decide if he or she is willing
 | 
			
		||||
to distribute software through any other system and a licensee cannot
 | 
			
		||||
impose that choice.
 | 
			
		||||
 | 
			
		||||
This section is intended to make thoroughly clear what is believed to
 | 
			
		||||
be a consequence of the rest of this License.
 | 
			
		||||
 | 
			
		||||
  8. If the distribution and/or use of the Program is restricted in
 | 
			
		||||
certain countries either by patents or by copyrighted interfaces, the
 | 
			
		||||
original copyright holder who places the Program under this License
 | 
			
		||||
may add an explicit geographical distribution limitation excluding
 | 
			
		||||
those countries, so that distribution is permitted only in or among
 | 
			
		||||
countries not thus excluded.  In such case, this License incorporates
 | 
			
		||||
the limitation as if written in the body of this License.
 | 
			
		||||
 | 
			
		||||
  9. The Free Software Foundation may publish revised and/or new versions
 | 
			
		||||
of the General Public License from time to time.  Such new versions will
 | 
			
		||||
be similar in spirit to the present version, but may differ in detail to
 | 
			
		||||
address new problems or concerns.
 | 
			
		||||
 | 
			
		||||
Each version is given a distinguishing version number.  If the Program
 | 
			
		||||
specifies a version number of this License which applies to it and "any
 | 
			
		||||
later version", you have the option of following the terms and conditions
 | 
			
		||||
either of that version or of any later version published by the Free
 | 
			
		||||
Software Foundation.  If the Program does not specify a version number of
 | 
			
		||||
this License, you may choose any version ever published by the Free Software
 | 
			
		||||
Foundation.
 | 
			
		||||
 | 
			
		||||
  10. If you wish to incorporate parts of the Program into other free
 | 
			
		||||
programs whose distribution conditions are different, write to the author
 | 
			
		||||
to ask for permission.  For software which is copyrighted by the Free
 | 
			
		||||
Software Foundation, write to the Free Software Foundation; we sometimes
 | 
			
		||||
make exceptions for this.  Our decision will be guided by the two goals
 | 
			
		||||
of preserving the free status of all derivatives of our free software and
 | 
			
		||||
of promoting the sharing and reuse of software generally.
 | 
			
		||||
 | 
			
		||||
                            NO WARRANTY
 | 
			
		||||
 | 
			
		||||
  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
 | 
			
		||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
 | 
			
		||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
 | 
			
		||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
 | 
			
		||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 | 
			
		||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
 | 
			
		||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
 | 
			
		||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
 | 
			
		||||
REPAIR OR CORRECTION.
 | 
			
		||||
 | 
			
		||||
  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
 | 
			
		||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
 | 
			
		||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
 | 
			
		||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
 | 
			
		||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
 | 
			
		||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
 | 
			
		||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
 | 
			
		||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
 | 
			
		||||
POSSIBILITY OF SUCH DAMAGES.
 | 
			
		||||
 | 
			
		||||
                     END OF TERMS AND CONDITIONS
 | 
			
		||||
 | 
			
		||||
            How to Apply These Terms to Your New Programs
 | 
			
		||||
 | 
			
		||||
  If you develop a new program, and you want it to be of the greatest
 | 
			
		||||
possible use to the public, the best way to achieve this is to make it
 | 
			
		||||
free software which everyone can redistribute and change under these terms.
 | 
			
		||||
 | 
			
		||||
  To do so, attach the following notices to the program.  It is safest
 | 
			
		||||
to attach them to the start of each source file to most effectively
 | 
			
		||||
convey the exclusion of warranty; and each file should have at least
 | 
			
		||||
the "copyright" line and a pointer to where the full notice is found.
 | 
			
		||||
 | 
			
		||||
    <one line to give the program's name and a brief idea of what it does.>
 | 
			
		||||
    Copyright (C) <year>  <name of author>
 | 
			
		||||
 | 
			
		||||
    This program is free software; you can redistribute it and/or modify
 | 
			
		||||
    it under the terms of the GNU General Public License as published by
 | 
			
		||||
    the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
    (at your option) any later version.
 | 
			
		||||
 | 
			
		||||
    This program is distributed in the hope that it will be useful,
 | 
			
		||||
    but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
    GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
    You should have received a copy of the GNU General Public License along
 | 
			
		||||
    with this program; if not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
Also add information on how to contact you by electronic and paper mail.
 | 
			
		||||
 | 
			
		||||
If the program is interactive, make it output a short notice like this
 | 
			
		||||
when it starts in an interactive mode:
 | 
			
		||||
 | 
			
		||||
    Gnomovision version 69, Copyright (C) year name of author
 | 
			
		||||
    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
 | 
			
		||||
    This is free software, and you are welcome to redistribute it
 | 
			
		||||
    under certain conditions; type `show c' for details.
 | 
			
		||||
 | 
			
		||||
The hypothetical commands `show w' and `show c' should show the appropriate
 | 
			
		||||
parts of the General Public License.  Of course, the commands you use may
 | 
			
		||||
be called something other than `show w' and `show c'; they could even be
 | 
			
		||||
mouse-clicks or menu items--whatever suits your program.
 | 
			
		||||
 | 
			
		||||
You should also get your employer (if you work as a programmer) or your
 | 
			
		||||
school, if any, to sign a "copyright disclaimer" for the program, if
 | 
			
		||||
necessary.  Here is a sample; alter the names:
 | 
			
		||||
 | 
			
		||||
  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
 | 
			
		||||
  `Gnomovision' (which makes passes at compilers) written by James Hacker.
 | 
			
		||||
 | 
			
		||||
  <signature of Moe Ghoul>, 1 April 1989
 | 
			
		||||
  Moe Ghoul, President of Vice
 | 
			
		||||
 | 
			
		||||
This General Public License does not permit incorporating your program into
 | 
			
		||||
proprietary programs.  If your program is a subroutine library, you may
 | 
			
		||||
consider it more useful to permit linking proprietary applications with the
 | 
			
		||||
library.  If this is what you want to do, use the GNU Lesser General
 | 
			
		||||
Public License instead of this License.
 | 
			
		||||
   Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
   distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
   See the License for the specific language governing permissions and
 | 
			
		||||
   limitations under the License.
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,7 @@
 | 
			
		||||
<!--
 | 
			
		||||
SPDX-License-Identifier: Apache-2.0
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
# DeTEE CLI
 | 
			
		||||
 | 
			
		||||
The DeTEE CLI will allow you to create VMs and containers on the [DeTEE decentralized cloud network](https://detee.ltd/).
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										2
									
								
								build.rs
									
									
									
									
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										2
									
								
								build.rs
									
									
									
									
									
								
							@ -1,3 +1,5 @@
 | 
			
		||||
// SPDX-License-Identifier: Apache-2.0
 | 
			
		||||
 | 
			
		||||
use shadow_rs::ShadowBuilder;
 | 
			
		||||
 | 
			
		||||
fn main() {
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,5 @@
 | 
			
		||||
# SPDX-License-Identifier: Apache-2.0
 | 
			
		||||
 | 
			
		||||
from archlinux:latest
 | 
			
		||||
copy tmp/.detee /root/.detee
 | 
			
		||||
run pacman -Syu --noconfirm
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,7 @@
 | 
			
		||||
#!/bin/bash
 | 
			
		||||
 | 
			
		||||
# SPDX-License-Identifier: Apache-2.0
 | 
			
		||||
 | 
			
		||||
cd -- "$( dirname -- "${BASH_SOURCE[0]}" )"
 | 
			
		||||
scriptdir="$(pwd)"
 | 
			
		||||
mkdir "${scriptdir}/tmp"
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,5 @@
 | 
			
		||||
# SPDX-License-Identifier: Apache-2.0
 | 
			
		||||
 | 
			
		||||
reorder_impl_items = true
 | 
			
		||||
use_small_heuristics = "Max"
 | 
			
		||||
imports_granularity = "Crate"
 | 
			
		||||
imports_granularity = "Module"
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,5 @@
 | 
			
		||||
# SPDX-License-Identifier: Apache-2.0
 | 
			
		||||
 | 
			
		||||
dtrfs_url: http://registry.detee.ltd/dtrfs-payments2025-01-23.cpio.gz
 | 
			
		||||
dtrfs_sha: 2e95d7969a0f2ae2ee6f37acd2789a032be1653e76ba93e607477c8b1cde42ed
 | 
			
		||||
kernel_url: http://registry.detee.ltd/vmlinuz-linux-6.12.10-arch1-1
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,5 @@
 | 
			
		||||
# SPDX-License-Identifier: Apache-2.0
 | 
			
		||||
 | 
			
		||||
node_pubkey: 3mWjE6FnKQ8f9WRjGHdj1Jtyewsri87GXQpqLWpwtjhr
 | 
			
		||||
package_url: https://registry.detee.ltd/sgx/packages/actix-app-info_package_2025-03-19_13-49-56.tar.gz
 | 
			
		||||
private_package: false
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,5 @@
 | 
			
		||||
# SPDX-License-Identifier: Apache-2.0
 | 
			
		||||
 | 
			
		||||
environments:
 | 
			
		||||
  - name: APP_NAME
 | 
			
		||||
    value: actix-test
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,5 @@
 | 
			
		||||
# SPDX-License-Identifier: Apache-2.0
 | 
			
		||||
 | 
			
		||||
hostname: my-specific-vm-01
 | 
			
		||||
price: 20000
 | 
			
		||||
hours: 5
 | 
			
		||||
@ -9,8 +11,8 @@ ipv4: !PublishPorts
 | 
			
		||||
# ipv4: !PublishPorts [ 80, 8080 ]
 | 
			
		||||
public_ipv6: false
 | 
			
		||||
vcpus: 2
 | 
			
		||||
memory_mb: 2000
 | 
			
		||||
disk_size_gb: 20
 | 
			
		||||
memory_gib: 2000
 | 
			
		||||
disk_size_gib: 20
 | 
			
		||||
# os_setup is an optional field that allows you to specify the operating system
 | 
			
		||||
# dtrfs is the DeTEE initramfs required to boot a VM. It also needs a kernel.
 | 
			
		||||
# The OS Template is normally a Linux distribution (without initrd and kernel)
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,5 @@
 | 
			
		||||
# SPDX-License-Identifier: Apache-2.0
 | 
			
		||||
 | 
			
		||||
hostname: my-vm-01
 | 
			
		||||
hours: 5
 | 
			
		||||
price: 20000
 | 
			
		||||
@ -9,5 +11,5 @@ ipv4: !PublishPorts
 | 
			
		||||
# ipv4: !PublishPorts [ 80, 8080 ]
 | 
			
		||||
public_ipv6: false
 | 
			
		||||
vcpus: 2
 | 
			
		||||
memory_mb: 2000
 | 
			
		||||
disk_size_gb: 20
 | 
			
		||||
memory_gib: 2
 | 
			
		||||
disk_size_gib: 20
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,5 @@
 | 
			
		||||
# SPDX-License-Identifier: Apache-2.0
 | 
			
		||||
 | 
			
		||||
hostname: my-public-vm-01
 | 
			
		||||
hours: 5
 | 
			
		||||
price: 20000
 | 
			
		||||
@ -11,5 +13,5 @@ ipv4: !PublicIPv4
 | 
			
		||||
# For IPv6, just specify true or false if you want a public IP
 | 
			
		||||
public_ipv6: true
 | 
			
		||||
vcpus: 2
 | 
			
		||||
memory_mb: 2000
 | 
			
		||||
disk_size_gb: 20
 | 
			
		||||
memory_gib: 2
 | 
			
		||||
disk_size_gib: 20
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,5 @@
 | 
			
		||||
# SPDX-License-Identifier: Apache-2.0
 | 
			
		||||
 | 
			
		||||
hostname: my-bucharest-vm-01
 | 
			
		||||
hours: 5
 | 
			
		||||
price: 20000
 | 
			
		||||
@ -10,5 +12,5 @@ location:
 | 
			
		||||
ipv4: !PublicIPv4
 | 
			
		||||
public_ipv6: false
 | 
			
		||||
vcpus: 2
 | 
			
		||||
memory_mb: 1000
 | 
			
		||||
disk_size_gb: 20
 | 
			
		||||
memory_gib: 1000
 | 
			
		||||
disk_size_gib: 20
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,7 @@
 | 
			
		||||
#!/bin/bash
 | 
			
		||||
 | 
			
		||||
# SPDX-License-Identifier: Apache-2.0
 | 
			
		||||
 | 
			
		||||
set -e
 | 
			
		||||
cd -- "$( dirname -- "${BASH_SOURCE[0]}" )"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,7 @@
 | 
			
		||||
#!/bin/bash
 | 
			
		||||
 | 
			
		||||
# SPDX-License-Identifier: Apache-2.0
 | 
			
		||||
 | 
			
		||||
cd -- "$( dirname -- "${BASH_SOURCE[0]}" )"
 | 
			
		||||
scriptdir="$(pwd)"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,7 @@
 | 
			
		||||
use clap::{builder::PossibleValue, Arg, Command};
 | 
			
		||||
// SPDX-License-Identifier: Apache-2.0
 | 
			
		||||
 | 
			
		||||
use clap::builder::PossibleValue;
 | 
			
		||||
use clap::{Arg, Command};
 | 
			
		||||
use detee_cli::general::cli_handler::{
 | 
			
		||||
    handle_account, handle_completion, handle_operators, handle_packagers,
 | 
			
		||||
};
 | 
			
		||||
@ -51,6 +54,16 @@ fn main() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn clap_cmd() -> Command {
 | 
			
		||||
    let snp_locations = [
 | 
			
		||||
        PossibleValue::new("GB").help("London, England, GB"),
 | 
			
		||||
        PossibleValue::new("Canada").help("Montréal or Vancouver"),
 | 
			
		||||
        PossibleValue::new("Montreal").help("Montréal, Quebec, CA"),
 | 
			
		||||
        PossibleValue::new("Vancouver").help("Vancouver, British Columbia, CA"),
 | 
			
		||||
        PossibleValue::new("California").help("San Jose, California, US"),
 | 
			
		||||
        PossibleValue::new("US").help("San Jose, California, US"),
 | 
			
		||||
        PossibleValue::new("France").help("Paris, Île-de-France, FR"),
 | 
			
		||||
        PossibleValue::new("Any").help("List offers for any location."),
 | 
			
		||||
    ];
 | 
			
		||||
    Command::new("detee-cli")
 | 
			
		||||
        .version(build::CLAP_LONG_VERSION)
 | 
			
		||||
        .author("https://detee.ltd")
 | 
			
		||||
@ -102,6 +115,7 @@ fn clap_cmd() -> Command {
 | 
			
		||||
                .subcommand(
 | 
			
		||||
                    Command::new("deploy")
 | 
			
		||||
                    .about("create new app from a YAML configuration file")
 | 
			
		||||
                    /*
 | 
			
		||||
                    .arg(
 | 
			
		||||
                        Arg::new("yaml-path")
 | 
			
		||||
                        .long("from-yaml")
 | 
			
		||||
@ -110,6 +124,7 @@ fn clap_cmd() -> Command {
 | 
			
		||||
                            "\n- deploying to a specific node or to a specific city.")
 | 
			
		||||
                        .exclusive(true)
 | 
			
		||||
                    )
 | 
			
		||||
                    */
 | 
			
		||||
                    .arg(
 | 
			
		||||
                        Arg::new("vcpus")
 | 
			
		||||
                        .long("vcpus")
 | 
			
		||||
@ -127,9 +142,9 @@ fn clap_cmd() -> Command {
 | 
			
		||||
                    .arg(
 | 
			
		||||
                        Arg::new("disk")
 | 
			
		||||
                        .long("disk")
 | 
			
		||||
                        .default_value("1000")
 | 
			
		||||
                        .value_parser(clap::value_parser!(u32).range(300..8000))
 | 
			
		||||
                        .help("disk size in MB")
 | 
			
		||||
                        .default_value("2")
 | 
			
		||||
                        .value_parser(clap::value_parser!(u32).range(1..100))
 | 
			
		||||
                        .help("disk size in GB")
 | 
			
		||||
                    )
 | 
			
		||||
                    .arg(
 | 
			
		||||
                        Arg::new("port")
 | 
			
		||||
@ -158,22 +173,23 @@ fn clap_cmd() -> Command {
 | 
			
		||||
                        .help("for how many hours should the app run")
 | 
			
		||||
                        .default_value("1")
 | 
			
		||||
                        .value_parser(clap::value_parser!(u64).range(1..5000))
 | 
			
		||||
                        .long_help("How long should the app run for so it locks up LP accordingly")
 | 
			
		||||
                        .long_help("How long should the app run for so it locks up credits accordingly")
 | 
			
		||||
                    )
 | 
			
		||||
                    .arg(
 | 
			
		||||
                        Arg::new("price")
 | 
			
		||||
                        .long("price")
 | 
			
		||||
                        .help("price per unit per minute; check docs")
 | 
			
		||||
                        .default_value("200000")
 | 
			
		||||
                        .help("maxium accepted price per unit per minute")
 | 
			
		||||
                        .default_value("4000")
 | 
			
		||||
                        .value_parser(clap::value_parser!(u64).range(1..50000000))
 | 
			
		||||
                    )
 | 
			
		||||
                    .arg(
 | 
			
		||||
                        Arg::new("location")
 | 
			
		||||
                        .help("deploy to a specific location")
 | 
			
		||||
                        .long("location")
 | 
			
		||||
                        .default_value("DE")
 | 
			
		||||
                        .default_value("Any")
 | 
			
		||||
                        .value_parser([
 | 
			
		||||
                            PossibleValue::new("DE").help("Frankfurt am Main, Hesse, Germany"),
 | 
			
		||||
                            PossibleValue::new("Any").help("List offers for any location."),
 | 
			
		||||
                        ]),
 | 
			
		||||
                    )
 | 
			
		||||
                    .arg(
 | 
			
		||||
@ -323,17 +339,8 @@ fn clap_cmd() -> Command {
 | 
			
		||||
                    Arg::new("location")
 | 
			
		||||
                    .help("deploy to a specific location")
 | 
			
		||||
                    .long("location")
 | 
			
		||||
                    .default_value("Vancouver")
 | 
			
		||||
                    .value_parser([
 | 
			
		||||
                        PossibleValue::new("GB").help("London, England, GB"),
 | 
			
		||||
                        PossibleValue::new("Canada").help("Montréal or Vancouver"),
 | 
			
		||||
                        PossibleValue::new("Montreal").help("Montréal, Quebec, CA"),
 | 
			
		||||
                        PossibleValue::new("Vancouver").help("Vancouver, British Columbia, CA"),
 | 
			
		||||
                        PossibleValue::new("California").help("San Jose, California, US"),
 | 
			
		||||
                        PossibleValue::new("US").help("San Jose, California, US"),
 | 
			
		||||
                        PossibleValue::new("France").help("Paris, Île-de-France, FR"),
 | 
			
		||||
                        PossibleValue::new("Random").help("Just deploy somewhere..."),
 | 
			
		||||
                    ]),
 | 
			
		||||
                    .default_value("Any")
 | 
			
		||||
                    .value_parser(snp_locations.clone()),
 | 
			
		||||
                )
 | 
			
		||||
                .arg(
 | 
			
		||||
                    Arg::new("vcpus")
 | 
			
		||||
@ -345,16 +352,16 @@ fn clap_cmd() -> Command {
 | 
			
		||||
                .arg(
 | 
			
		||||
                    Arg::new("memory")
 | 
			
		||||
                    .long("memory")
 | 
			
		||||
                    .default_value("1000")
 | 
			
		||||
                    .value_parser(clap::value_parser!(u32).range(800..123000))
 | 
			
		||||
                    .help("memory in MB")
 | 
			
		||||
                    .default_value("1")
 | 
			
		||||
                    .value_parser(clap::value_parser!(u32).range(1..500))
 | 
			
		||||
                    .help("memory in GiB")
 | 
			
		||||
                )
 | 
			
		||||
                .arg(
 | 
			
		||||
                    Arg::new("disk")
 | 
			
		||||
                    .long("disk")
 | 
			
		||||
                    .default_value("10")
 | 
			
		||||
                    .value_parser(clap::value_parser!(u32).range(5..500))
 | 
			
		||||
                    .help("disk size in GB")
 | 
			
		||||
                    .help("disk size in GiB")
 | 
			
		||||
                )
 | 
			
		||||
                .arg(
 | 
			
		||||
                    Arg::new("distribution")
 | 
			
		||||
@ -373,8 +380,8 @@ fn clap_cmd() -> Command {
 | 
			
		||||
                .arg(
 | 
			
		||||
                    Arg::new("price")
 | 
			
		||||
                    .long("price")
 | 
			
		||||
                    .help("price per unit per minute; check docs")
 | 
			
		||||
                    .default_value("20000")
 | 
			
		||||
                    .help("maxium accepted price per unit per minute")
 | 
			
		||||
                    .default_value("4000")
 | 
			
		||||
                    .value_parser(clap::value_parser!(u64).range(1..50000000))
 | 
			
		||||
                )
 | 
			
		||||
                .arg(
 | 
			
		||||
@ -435,7 +442,7 @@ fn clap_cmd() -> Command {
 | 
			
		||||
                .long_about("Allows you to update the hardware or the lifetime".to_string() +
 | 
			
		||||
                    "\nAny hardware modifiations will restart the VM." +
 | 
			
		||||
                    "\nChanging the lifetime of a VM will not restart." +
 | 
			
		||||
                    "\nIf changing the lifetime to a higher value, LP will locked accordingly.")
 | 
			
		||||
                    "\nIf changing the lifetime to a higher value, credits will locked accordingly.")
 | 
			
		||||
                .arg(
 | 
			
		||||
                    Arg::new("uuid")
 | 
			
		||||
                    .help("supply the uuid of the VM you wish to upgrade")
 | 
			
		||||
@ -458,15 +465,15 @@ fn clap_cmd() -> Command {
 | 
			
		||||
                    Arg::new("memory")
 | 
			
		||||
                    .long("memory")
 | 
			
		||||
                    .default_value("0")
 | 
			
		||||
                    .value_parser(clap::value_parser!(u32).range(0..115000))
 | 
			
		||||
                    .help("modify the MB of memory reserved")
 | 
			
		||||
                    .value_parser(clap::value_parser!(u32).range(0..5000))
 | 
			
		||||
                    .help("modify the GiB of memory reserved")
 | 
			
		||||
                )
 | 
			
		||||
                .arg(
 | 
			
		||||
                    Arg::new("disk")
 | 
			
		||||
                    .long("disk")
 | 
			
		||||
                    .default_value("0")
 | 
			
		||||
                    .value_parser(clap::value_parser!(u32).range(0..500))
 | 
			
		||||
                    .help("increase the size of the disk in GB")
 | 
			
		||||
                    .help("increase the size of the disk in GiB")
 | 
			
		||||
                )
 | 
			
		||||
                .arg(
 | 
			
		||||
                    Arg::new("hours")
 | 
			
		||||
@ -499,7 +506,24 @@ fn clap_cmd() -> Command {
 | 
			
		||||
        )
 | 
			
		||||
        .subcommand(Command::new("vm-node")
 | 
			
		||||
            .about("info about AMD SEV-SNP servers registerd to DeTEE")
 | 
			
		||||
            .subcommand(Command::new("search").about("search nodes based on filters"))
 | 
			
		||||
            .subcommand(Command::new("search").about("search nodes based on filters")
 | 
			
		||||
                .arg(
 | 
			
		||||
                    Arg::new("location")
 | 
			
		||||
                    .help("deploy to a specific location")
 | 
			
		||||
                    .long("location")
 | 
			
		||||
                    .default_value("Any")
 | 
			
		||||
                    .value_parser(snp_locations.clone()),
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
            .subcommand(Command::new("offers").about("search nodes based on filters")
 | 
			
		||||
                .arg(
 | 
			
		||||
                    Arg::new("location")
 | 
			
		||||
                    .help("deploy to a specific location")
 | 
			
		||||
                    .long("location")
 | 
			
		||||
                    .default_value("Any")
 | 
			
		||||
                    .value_parser(snp_locations),
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
            .subcommand(Command::new("inspect").about("get detailed information about a node")
 | 
			
		||||
                .arg(
 | 
			
		||||
                    Arg::new("ip")
 | 
			
		||||
@ -535,12 +559,12 @@ fn clap_cmd() -> Command {
 | 
			
		||||
                .arg(
 | 
			
		||||
                    Arg::new("escrow")
 | 
			
		||||
                    .long("escrow")
 | 
			
		||||
                    .help("At least 5000 LP is required as escrow")
 | 
			
		||||
                    .help("At least 5000 credits is required as escrow")
 | 
			
		||||
                    .long_help("Escrow is used by node operators to guarantee quality.".to_owned() +
 | 
			
		||||
                        "\nBefore adding escrow, make sure you booted a node under your account." +
 | 
			
		||||
                        "\nWhen all your nodes got decomissioned, your escrow gets automatically returned.")
 | 
			
		||||
                    .default_value("5000")
 | 
			
		||||
                    .value_parser(clap::value_parser!(u64).range(5000..100000))
 | 
			
		||||
                    .value_parser(clap::value_parser!(u64).range(0..100000))
 | 
			
		||||
                )
 | 
			
		||||
                .arg(
 | 
			
		||||
                    Arg::new("email")
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,5 @@
 | 
			
		||||
// SPDX-License-Identifier: Apache-2.0
 | 
			
		||||
 | 
			
		||||
use clap::{Arg, ArgMatches, Command};
 | 
			
		||||
use clap_complete::{generate, Shell};
 | 
			
		||||
use detee_cli::config::Config;
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,8 @@
 | 
			
		||||
use crate::{general, utils::block_on};
 | 
			
		||||
// SPDX-License-Identifier: Apache-2.0
 | 
			
		||||
 | 
			
		||||
use crate::constants::{BRAIN_STAGING, BRAIN_TESTING};
 | 
			
		||||
use crate::general;
 | 
			
		||||
use crate::utils::block_on;
 | 
			
		||||
use ed25519_dalek::SigningKey;
 | 
			
		||||
use log::{debug, info, warn};
 | 
			
		||||
use openssl::bn::BigNum;
 | 
			
		||||
@ -6,7 +10,9 @@ use openssl::hash::{Hasher, MessageDigest};
 | 
			
		||||
use openssl::pkey::{PKey, Private};
 | 
			
		||||
use openssl::rsa::Rsa;
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
use std::{fs::File, io::Write, path::Path};
 | 
			
		||||
use std::fs::File;
 | 
			
		||||
use std::io::Write;
 | 
			
		||||
use std::path::Path;
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Default)]
 | 
			
		||||
pub struct AccountData {
 | 
			
		||||
@ -33,10 +39,10 @@ impl super::HumanOutput for AccountData {
 | 
			
		||||
        }
 | 
			
		||||
        if !self.wallet_path.is_empty() {
 | 
			
		||||
            println!("The address of your DeTEE wallet is {}", self.wallet_address);
 | 
			
		||||
            println!("The balance of your account is {} LP", self.account_balance);
 | 
			
		||||
            println!("The balance of your account is {} credits", self.account_balance);
 | 
			
		||||
            if self.locked_funds != 0.0 {
 | 
			
		||||
                println!(
 | 
			
		||||
                    "WARNING! {} LP is temporary locked, waiting for a Contract.",
 | 
			
		||||
                    "WARNING! {} credits is temporary locked, waiting for a Contract.",
 | 
			
		||||
                    self.locked_funds
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
@ -309,20 +315,30 @@ impl Config {
 | 
			
		||||
 | 
			
		||||
    pub fn get_brain_info() -> (String, String) {
 | 
			
		||||
        match Self::init_config().network.as_str() {
 | 
			
		||||
            "staging" => ("https://159.65.58.38:31337".to_string(), "staging-brain".to_string()),
 | 
			
		||||
            "localhost" => ("https://localhost:31337".to_string(), "staging-brain".to_string()),
 | 
			
		||||
            _ => ("https://164.92.249.180:31337".to_string(), "testnet-brain".to_string()),
 | 
			
		||||
            "staging" => {
 | 
			
		||||
                let url = BRAIN_STAGING.to_string();
 | 
			
		||||
                log::info!("Using staging brain URL: {url}");
 | 
			
		||||
                (url, "staging-brain".to_string())
 | 
			
		||||
            }
 | 
			
		||||
            _ => {
 | 
			
		||||
                let url = BRAIN_TESTING.to_string();
 | 
			
		||||
                log::info!("Using testnet brain URL: {url}");
 | 
			
		||||
                (url, "testnet-brain".to_string())
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub async fn get_brain_channel() -> Result<tonic::transport::Channel, Error> {
 | 
			
		||||
        let (brain_url, brain_san) = Self::get_brain_info();
 | 
			
		||||
 | 
			
		||||
    pub async fn connect_brain_channel(
 | 
			
		||||
        brain_url: String,
 | 
			
		||||
    ) -> Result<tonic::transport::Channel, Error> {
 | 
			
		||||
        use hyper_rustls::HttpsConnectorBuilder;
 | 
			
		||||
        use rustls::pki_types::pem::PemObject;
 | 
			
		||||
        use rustls::pki_types::CertificateDer;
 | 
			
		||||
        use rustls::{ClientConfig, RootCertStore};
 | 
			
		||||
 | 
			
		||||
        let brain_san = Config::get_brain_info().1;
 | 
			
		||||
 | 
			
		||||
        let mut detee_root_ca_store = RootCertStore::empty();
 | 
			
		||||
        detee_root_ca_store
 | 
			
		||||
            .add(CertificateDer::from_pem_file(Config::get_root_ca_path()?).map_err(|e| {
 | 
			
		||||
 | 
			
		||||
@ -1 +1,25 @@
 | 
			
		||||
// SPDX-License-Identifier: Apache-2.0
 | 
			
		||||
 | 
			
		||||
use rand::Rng;
 | 
			
		||||
use std::sync::LazyLock;
 | 
			
		||||
 | 
			
		||||
pub const HRATLS_APP_PORT: u32 = 34500;
 | 
			
		||||
pub const MAX_REDIRECTS: u16 = 3;
 | 
			
		||||
 | 
			
		||||
pub const STAGING_BRAIN_URLS: [&str; 3] = [
 | 
			
		||||
    "https://156.146.63.216:31337", // staging brain 1
 | 
			
		||||
    "https://156.146.63.216:31337", // staging brain 2
 | 
			
		||||
    "https://156.146.63.216:31337", // staging brain 3
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
pub const TESTNET_BRAIN_URLS: [&str; 3] = [
 | 
			
		||||
    "https://156.146.63.218:31337", // testnet brain 1
 | 
			
		||||
    "https://156.146.63.218:31337", // testnet brain 2
 | 
			
		||||
    "https://156.146.63.218:31337", // testnet brain 3
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
pub static BRAIN_STAGING: LazyLock<&str> =
 | 
			
		||||
    LazyLock::new(|| STAGING_BRAIN_URLS[rand::thread_rng().gen_range(0..STAGING_BRAIN_URLS.len())]);
 | 
			
		||||
 | 
			
		||||
pub static BRAIN_TESTING: LazyLock<&str> =
 | 
			
		||||
    LazyLock::new(|| TESTNET_BRAIN_URLS[rand::thread_rng().gen_range(0..TESTNET_BRAIN_URLS.len())]);
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,8 @@
 | 
			
		||||
use super::operators;
 | 
			
		||||
use super::packagers;
 | 
			
		||||
// SPDX-License-Identifier: Apache-2.0
 | 
			
		||||
 | 
			
		||||
use super::{operators, packagers};
 | 
			
		||||
use crate::{cli_print, config};
 | 
			
		||||
use clap::ArgMatches;
 | 
			
		||||
use clap::Command;
 | 
			
		||||
use clap::{ArgMatches, Command};
 | 
			
		||||
use clap_complete::{generate, Shell};
 | 
			
		||||
use std::error::Error;
 | 
			
		||||
use std::io;
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,5 @@
 | 
			
		||||
// SPDX-License-Identifier: Apache-2.0
 | 
			
		||||
 | 
			
		||||
use crate::config::Config;
 | 
			
		||||
use crate::snp::grpc::proto::VmContract;
 | 
			
		||||
use crate::utils::sign_request;
 | 
			
		||||
@ -35,7 +37,8 @@ pub enum Error {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async fn client() -> Result<BrainGeneralCliClient<Channel>, Error> {
 | 
			
		||||
    Ok(BrainGeneralCliClient::new(Config::get_brain_channel().await?))
 | 
			
		||||
    let default_brain_url = Config::get_brain_info().0;
 | 
			
		||||
    Ok(BrainGeneralCliClient::new(Config::connect_brain_channel(default_brain_url).await?))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub async fn get_balance(account: &str) -> Result<AccountBalance, Error> {
 | 
			
		||||
@ -93,7 +96,7 @@ pub async fn kick_contract(contract_uuid: String, reason: String) -> Result<u64,
 | 
			
		||||
        })?)
 | 
			
		||||
        .await?
 | 
			
		||||
        .into_inner()
 | 
			
		||||
        .nano_lp)
 | 
			
		||||
        .nano_credits)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub async fn ban_user(user_wallet: String) -> Result<(), Error> {
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,5 @@
 | 
			
		||||
// SPDX-License-Identifier: Apache-2.0
 | 
			
		||||
 | 
			
		||||
pub mod cli_handler;
 | 
			
		||||
pub mod grpc;
 | 
			
		||||
pub mod operators;
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,5 @@
 | 
			
		||||
// SPDX-License-Identifier: Apache-2.0
 | 
			
		||||
 | 
			
		||||
use crate::general::grpc;
 | 
			
		||||
use crate::utils::block_on;
 | 
			
		||||
use tabled::Tabled;
 | 
			
		||||
@ -16,7 +18,7 @@ impl From<grpc::proto::ListOperatorsResp> for TabledOperator {
 | 
			
		||||
    fn from(brain_operator: grpc::proto::ListOperatorsResp) -> Self {
 | 
			
		||||
        TabledOperator {
 | 
			
		||||
            wallet: brain_operator.pubkey,
 | 
			
		||||
            escrow: brain_operator.escrow,
 | 
			
		||||
            escrow: brain_operator.escrow / 1_000_000_000,
 | 
			
		||||
            email: brain_operator.email,
 | 
			
		||||
            app_nodes: brain_operator.app_nodes,
 | 
			
		||||
            vm_nodes: brain_operator.vm_nodes,
 | 
			
		||||
@ -33,7 +35,7 @@ pub fn register(escrow: u64, email: String) -> Result<crate::SimpleOutput, grpc:
 | 
			
		||||
impl crate::HumanOutput for grpc::proto::InspectOperatorResp {
 | 
			
		||||
    fn human_cli_print(&self) {
 | 
			
		||||
        if let Some(op) = &self.operator {
 | 
			
		||||
            println!("The operator {} supplies {} nanoLP as escrow,", op.pubkey, op.escrow,);
 | 
			
		||||
            println!("The operator {} supplies {} nanocredits as escrow,", op.pubkey, op.escrow,);
 | 
			
		||||
            println!(
 | 
			
		||||
                "has {} app servers, {} VM servers, and {} total reports for all servers.",
 | 
			
		||||
                op.app_nodes, op.vm_nodes, op.reports
 | 
			
		||||
@ -75,7 +77,7 @@ pub fn print_operators() -> Result<Vec<grpc::proto::ListOperatorsResp>, grpc::Er
 | 
			
		||||
pub fn kick(contract_uuid: String, reason: String) -> Result<crate::SimpleOutput, grpc::Error> {
 | 
			
		||||
    let nano_lp = block_on(grpc::kick_contract(contract_uuid, reason))?;
 | 
			
		||||
    Ok(crate::SimpleOutput::from(
 | 
			
		||||
        format!("Successfully terminated contract. Refunded {} nanoLP.", nano_lp).as_str(),
 | 
			
		||||
        format!("Successfully terminated contract. Refunded {} nanocredits.", nano_lp).as_str(),
 | 
			
		||||
    ))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,5 @@
 | 
			
		||||
// SPDX-License-Identifier: Apache-2.0
 | 
			
		||||
 | 
			
		||||
use serde::Serialize;
 | 
			
		||||
use tabled::Tabled;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,5 @@
 | 
			
		||||
// SPDX-License-Identifier: Apache-2.0
 | 
			
		||||
 | 
			
		||||
pub mod config;
 | 
			
		||||
pub mod constants;
 | 
			
		||||
pub mod general;
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,7 @@
 | 
			
		||||
#![allow(dead_code)]
 | 
			
		||||
 | 
			
		||||
// SPDX-License-Identifier: Apache-2.0
 | 
			
		||||
 | 
			
		||||
use rand::Rng;
 | 
			
		||||
 | 
			
		||||
pub fn random_app_name() -> String {
 | 
			
		||||
@ -377,4 +380,3 @@ const APP_SUBSTANTIVES: [&str; 70] = [
 | 
			
		||||
    "gecko",
 | 
			
		||||
    "zebra",
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,24 +1,20 @@
 | 
			
		||||
// SPDX-License-Identifier: Apache-2.0
 | 
			
		||||
 | 
			
		||||
use crate::config::Config;
 | 
			
		||||
use crate::name_generator::random_app_name;
 | 
			
		||||
use crate::sgx::config::{validate_yaml, DeteeCliExt};
 | 
			
		||||
use crate::sgx::config::validate_yaml;
 | 
			
		||||
use crate::sgx::deploy::Reqwest;
 | 
			
		||||
use crate::sgx::grpc_brain::{delete_app, list_contracts};
 | 
			
		||||
use crate::sgx::grpc_dtpm::{get_config, update_config};
 | 
			
		||||
use crate::sgx::packaging::package_enclave;
 | 
			
		||||
use crate::sgx::utils::{
 | 
			
		||||
    deploy_new_app_and_update_config, fetch_config, override_envs_and_args_launch_config,
 | 
			
		||||
};
 | 
			
		||||
use crate::sgx::AppDeleteResponse;
 | 
			
		||||
use crate::sgx::{
 | 
			
		||||
    append_uuid_list, get_app_node, get_app_node_by_contract, get_one_contract, inspect_node,
 | 
			
		||||
    package_entry_from_name, print_nodes, write_uuid_list,
 | 
			
		||||
    get_app_node_by_contract, get_one_contract, inspect_node, print_nodes, write_uuid_list,
 | 
			
		||||
    AppContract, AppDeleteResponse, AppDeployResponse,
 | 
			
		||||
};
 | 
			
		||||
use crate::sgx::{AppContract, AppDeployResponse};
 | 
			
		||||
use crate::utils::block_on;
 | 
			
		||||
use crate::{cli_print, SimpleOutput};
 | 
			
		||||
use clap::ArgMatches;
 | 
			
		||||
use detee_shared::app_proto::ListAppContractsReq;
 | 
			
		||||
use detee_shared::sgx::types::brain::AppDeployConfig;
 | 
			
		||||
use detee_shared::sgx::types::brain::Resource;
 | 
			
		||||
 | 
			
		||||
pub fn handle_app(app_matche: &ArgMatches) {
 | 
			
		||||
    match app_matche.subcommand() {
 | 
			
		||||
@ -74,78 +70,37 @@ fn handle_package(package_match: &ArgMatches) -> Result<SimpleOutput, Box<dyn st
 | 
			
		||||
fn handle_deploy(
 | 
			
		||||
    deploy_match: &ArgMatches,
 | 
			
		||||
) -> Result<AppDeployResponse, Box<dyn std::error::Error>> {
 | 
			
		||||
    let (mut app_deploy_config, app_launch_config) = if let Some(file_path) =
 | 
			
		||||
        deploy_match.get_one::<String>("yaml-path")
 | 
			
		||||
    {
 | 
			
		||||
        // TODO: maybe add launch config on deploy command with --launch-config flag
 | 
			
		||||
        (AppDeployConfig::from_path(file_path).unwrap(), None)
 | 
			
		||||
    } else {
 | 
			
		||||
        let vcpu = *deploy_match.get_one::<u32>("vcpus").unwrap();
 | 
			
		||||
        let memory_mb = *deploy_match.get_one::<u32>("memory").unwrap();
 | 
			
		||||
        let disk_mb = *deploy_match.get_one::<u32>("disk").unwrap();
 | 
			
		||||
        let port =
 | 
			
		||||
            deploy_match.get_many::<u32>("port").unwrap_or_default().cloned().collect::<Vec<_>>();
 | 
			
		||||
        let package_name = deploy_match.get_one::<String>("package").unwrap().clone();
 | 
			
		||||
        let hours = *deploy_match.get_one::<u64>("hours").unwrap();
 | 
			
		||||
        let node_unit_price = *deploy_match.get_one::<u64>("price").unwrap();
 | 
			
		||||
        let location = deploy_match.get_one::<String>("location").unwrap().as_str();
 | 
			
		||||
        let app_name =
 | 
			
		||||
            deploy_match.get_one::<String>("name").cloned().unwrap_or_else(random_app_name);
 | 
			
		||||
        let envs =
 | 
			
		||||
            deploy_match.get_many::<String>("env").unwrap_or_default().cloned().collect::<Vec<_>>();
 | 
			
		||||
        let args =
 | 
			
		||||
            deploy_match.get_many::<String>("arg").unwrap_or_default().cloned().collect::<Vec<_>>();
 | 
			
		||||
    let vcpus = *deploy_match.get_one::<u32>("vcpus").unwrap();
 | 
			
		||||
    let memory_mib = *deploy_match.get_one::<u32>("memory").unwrap();
 | 
			
		||||
    let disk_size_mib = *deploy_match.get_one::<u32>("disk").unwrap() * 1024;
 | 
			
		||||
    let port =
 | 
			
		||||
        deploy_match.get_many::<u32>("port").unwrap_or_default().cloned().collect::<Vec<_>>();
 | 
			
		||||
    let package_name = deploy_match.get_one::<String>("package").unwrap().clone();
 | 
			
		||||
    let hours = *deploy_match.get_one::<u64>("hours").unwrap();
 | 
			
		||||
    let price = *deploy_match.get_one::<u64>("price").unwrap();
 | 
			
		||||
    let location = deploy_match.get_one::<String>("location").unwrap().clone();
 | 
			
		||||
    let app_name = deploy_match.get_one::<String>("name").cloned().unwrap_or_else(random_app_name);
 | 
			
		||||
    let envs =
 | 
			
		||||
        deploy_match.get_many::<String>("env").unwrap_or_default().cloned().collect::<Vec<_>>();
 | 
			
		||||
    let args =
 | 
			
		||||
        deploy_match.get_many::<String>("arg").unwrap_or_default().cloned().collect::<Vec<_>>();
 | 
			
		||||
 | 
			
		||||
        let private_package = false;
 | 
			
		||||
    let app_deploy_config = Reqwest {
 | 
			
		||||
        app_name,
 | 
			
		||||
        package_name,
 | 
			
		||||
        vcpus,
 | 
			
		||||
        memory_mib,
 | 
			
		||||
        disk_size_mib,
 | 
			
		||||
        port,
 | 
			
		||||
        hours,
 | 
			
		||||
        location,
 | 
			
		||||
        price,
 | 
			
		||||
 | 
			
		||||
        let resource = Resource { vcpu, memory_mb, disk_mb, port };
 | 
			
		||||
        let node_pubkey = match block_on(get_app_node(resource.clone(), location.into())) {
 | 
			
		||||
            Ok(node) => node.node_pubkey,
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                return Err(Box::new(std::io::Error::other(
 | 
			
		||||
                    format!("Could not get node pubkey due to error: {:?}", e).as_str(),
 | 
			
		||||
                )));
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let package_entry = package_entry_from_name(&package_name).unwrap();
 | 
			
		||||
        let package_url = package_entry.package_url.clone();
 | 
			
		||||
        let public_package_mr_enclave = Some(package_entry.mr_enclave.to_vec());
 | 
			
		||||
 | 
			
		||||
        let config = block_on(fetch_config(&package_name))?;
 | 
			
		||||
 | 
			
		||||
        let launch_config = override_envs_and_args_launch_config(config, envs, args);
 | 
			
		||||
 | 
			
		||||
        (
 | 
			
		||||
            AppDeployConfig {
 | 
			
		||||
                package_url,
 | 
			
		||||
                resource,
 | 
			
		||||
                node_unit_price,
 | 
			
		||||
                hours,
 | 
			
		||||
                node_pubkey,
 | 
			
		||||
                private_package,
 | 
			
		||||
                app_name,
 | 
			
		||||
                public_package_mr_enclave,
 | 
			
		||||
                ..Default::default()
 | 
			
		||||
            },
 | 
			
		||||
            Some(launch_config),
 | 
			
		||||
        )
 | 
			
		||||
        envs,
 | 
			
		||||
        args,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    if app_deploy_config.app_name.is_empty() {
 | 
			
		||||
        app_deploy_config.app_name = random_app_name();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let app_name = app_deploy_config.app_name.clone();
 | 
			
		||||
 | 
			
		||||
    match block_on(deploy_new_app_and_update_config(app_deploy_config, app_launch_config)) {
 | 
			
		||||
        Ok(new_app_res) if new_app_res.error.is_empty() => {
 | 
			
		||||
            append_uuid_list(&new_app_res.uuid, &app_name)?;
 | 
			
		||||
            Ok((new_app_res, app_name).into())
 | 
			
		||||
        }
 | 
			
		||||
        Ok(new_app_res) => Err(Box::new(std::io::Error::other(new_app_res.error))),
 | 
			
		||||
        Err(e) => Err(Box::new(e)),
 | 
			
		||||
    }
 | 
			
		||||
    Ok(block_on(app_deploy_config.deploy())?)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn handle_inspect(inspect_match: &ArgMatches) {
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,7 @@
 | 
			
		||||
use detee_shared::sgx::types::{brain::AppDeployConfig, dtpm::DtpmConfig};
 | 
			
		||||
// SPDX-License-Identifier: Apache-2.0
 | 
			
		||||
 | 
			
		||||
use detee_shared::sgx::types::brain::AppDeployConfig;
 | 
			
		||||
use detee_shared::sgx::types::dtpm::DtpmConfig;
 | 
			
		||||
 | 
			
		||||
#[derive(thiserror::Error, Debug)]
 | 
			
		||||
pub enum Error {
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										177
									
								
								src/sgx/deploy.rs
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										177
									
								
								src/sgx/deploy.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,177 @@
 | 
			
		||||
use crate::config::Config;
 | 
			
		||||
use crate::name_generator::random_app_name;
 | 
			
		||||
use crate::sgx::grpc_brain::{get_app_node_list, new_app};
 | 
			
		||||
use crate::sgx::grpc_dtpm::{dtpm_client, set_config_pb, upload_files_pb};
 | 
			
		||||
use crate::sgx::utils::{
 | 
			
		||||
    calculate_nanocredits_for_app, fetch_config, hratls_url_and_mr_enclave_from_app_id,
 | 
			
		||||
};
 | 
			
		||||
use crate::sgx::{
 | 
			
		||||
    append_uuid_list, package_entry_from_name, AppDeployResponse, Error, PackageElement,
 | 
			
		||||
};
 | 
			
		||||
use crate::snp;
 | 
			
		||||
use detee_shared::app_proto::{AppNodeFilters, AppNodeListResp, AppResource, NewAppReq};
 | 
			
		||||
use detee_shared::sgx::pb::dtpm_proto::DtpmSetConfigReq;
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
use serde_default_utils::*;
 | 
			
		||||
use tokio_retry::strategy::FixedInterval;
 | 
			
		||||
use tokio_retry::Retry;
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
 | 
			
		||||
pub struct Reqwest {
 | 
			
		||||
    #[serde(default = "random_app_name")]
 | 
			
		||||
    pub app_name: String,
 | 
			
		||||
    pub package_name: String,
 | 
			
		||||
    pub vcpus: u32,
 | 
			
		||||
    pub memory_mib: u32,
 | 
			
		||||
    pub disk_size_mib: u32,
 | 
			
		||||
    pub port: Vec<u32>,
 | 
			
		||||
    #[serde(default = "default_u64::<1>")]
 | 
			
		||||
    pub hours: u64,
 | 
			
		||||
    #[serde(default)]
 | 
			
		||||
    pub price: u64,
 | 
			
		||||
    #[serde(default)]
 | 
			
		||||
    pub location: String,
 | 
			
		||||
 | 
			
		||||
    pub envs: Vec<String>,
 | 
			
		||||
    pub args: Vec<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Reqwest {
 | 
			
		||||
    pub async fn deploy(self) -> Result<AppDeployResponse, Error> {
 | 
			
		||||
        let mut req = self.get_cheapest_offer().await?;
 | 
			
		||||
 | 
			
		||||
        let PackageElement { package_url, mr_enclave, launch_config_url, .. } =
 | 
			
		||||
            package_entry_from_name(&self.package_name).expect("Unknown package name");
 | 
			
		||||
 | 
			
		||||
        let AppResource { vcpus, memory_mib, disk_size_mib, .. } =
 | 
			
		||||
            req.resource.clone().unwrap_or_default();
 | 
			
		||||
 | 
			
		||||
        req.public_package_mr_enclave = Some(mr_enclave.to_vec());
 | 
			
		||||
        req.admin_pubkey = Config::get_detee_wallet()?;
 | 
			
		||||
        req.hratls_pubkey = Config::get_hratls_pubkey_hex()?;
 | 
			
		||||
        req.package_url = package_url;
 | 
			
		||||
 | 
			
		||||
        eprintln!(
 | 
			
		||||
            "Node {} can offer the app at {} nanocredits for {} hours. Spec: {vcpus} vCPUs, {memory_mib} MiB mem, {disk_size_mib} MiB disk.",
 | 
			
		||||
            &req.node_pubkey, req.locked_nano, self.hours
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        let new_app_res = new_app(req).await?;
 | 
			
		||||
 | 
			
		||||
        if !new_app_res.error.is_empty() {
 | 
			
		||||
            return Err(Error::Deployment(new_app_res.error));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        tokio::time::sleep(tokio::time::Duration::from_millis(2500)).await;
 | 
			
		||||
 | 
			
		||||
        let (hratls_uri, mr_enclave) =
 | 
			
		||||
            hratls_url_and_mr_enclave_from_app_id(&new_app_res.uuid).await?;
 | 
			
		||||
 | 
			
		||||
        let mr_enclave = mr_enclave.expect("App contract does not have a mr_enclave");
 | 
			
		||||
 | 
			
		||||
        log::info!("hratls uri: {hratls_uri} mr_enclave: {mr_enclave:?}");
 | 
			
		||||
 | 
			
		||||
        if new_app_res.error.is_empty() {
 | 
			
		||||
            let launch_config = fetch_config(&launch_config_url).await?;
 | 
			
		||||
            eprint!("Deploying...");
 | 
			
		||||
            let dtpm_client = Retry::spawn(FixedInterval::from_millis(1000).take(30), || {
 | 
			
		||||
                log::debug!("retrying attestation and launch config update");
 | 
			
		||||
                eprint!(".");
 | 
			
		||||
                dtpm_client(&hratls_uri, &mr_enclave)
 | 
			
		||||
            })
 | 
			
		||||
            .await?;
 | 
			
		||||
            println!("");
 | 
			
		||||
            upload_files_pb(launch_config.filesystems.clone(), &dtpm_client).await?;
 | 
			
		||||
 | 
			
		||||
            let config_data = Some(launch_config.into());
 | 
			
		||||
            log::trace!("Decoded the configuration... {:?}", config_data);
 | 
			
		||||
            let req = DtpmSetConfigReq { config_data, ..Default::default() };
 | 
			
		||||
            set_config_pb(req, &dtpm_client).await?;
 | 
			
		||||
 | 
			
		||||
            append_uuid_list(&new_app_res.uuid, &self.app_name)?;
 | 
			
		||||
            Ok((new_app_res, self.app_name).into())
 | 
			
		||||
        } else {
 | 
			
		||||
            Err(Error::Deployment(new_app_res.error))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub async fn get_cheapest_offer(&self) -> Result<NewAppReq, Error> {
 | 
			
		||||
        let location = snp::Location::from(self.location.as_str());
 | 
			
		||||
        let app_node_filter = AppNodeFilters {
 | 
			
		||||
            vcpus: self.vcpus,
 | 
			
		||||
            memory_mib: self.memory_mib,
 | 
			
		||||
            storage_mib: self.disk_size_mib,
 | 
			
		||||
            country: location.country.clone().unwrap_or_default(),
 | 
			
		||||
            region: location.region.clone().unwrap_or_default(),
 | 
			
		||||
            city: location.city.clone().unwrap_or_default(),
 | 
			
		||||
            ip: location.node_ip.clone().unwrap_or_default(),
 | 
			
		||||
            node_pubkey: String::new(),
 | 
			
		||||
            free_ports: (self.port.len() + 1) as u32,
 | 
			
		||||
        };
 | 
			
		||||
        let node_list = get_app_node_list(app_node_filter).await?;
 | 
			
		||||
        let mut node_iter = node_list.iter();
 | 
			
		||||
 | 
			
		||||
        let mut final_req =
 | 
			
		||||
            self.calculate_app_request(node_iter.next().ok_or(Error::NoValidNodeFound)?);
 | 
			
		||||
 | 
			
		||||
        while let Some(node) = node_iter.next() {
 | 
			
		||||
            let new_app_req = self.calculate_app_request(node);
 | 
			
		||||
            if new_app_req.locked_nano < final_req.locked_nano {
 | 
			
		||||
                final_req = new_app_req;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Ok(final_req)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn calculate_app_request(&self, node: &AppNodeListResp) -> NewAppReq {
 | 
			
		||||
        let node_mem_per_cpu = node.memory_mib / node.vcpus;
 | 
			
		||||
        let node_disk_per_cpu = node.disk_mib / node.vcpus;
 | 
			
		||||
 | 
			
		||||
        let mut req_vcpus = self.vcpus;
 | 
			
		||||
 | 
			
		||||
        if req_vcpus < self.memory_mib.div_ceil(node_mem_per_cpu as u32) {
 | 
			
		||||
            req_vcpus = self.memory_mib.div_ceil(node_mem_per_cpu as u32);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if req_vcpus < self.disk_size_mib.div_ceil(node_disk_per_cpu as u32) {
 | 
			
		||||
            req_vcpus = self.disk_size_mib.div_ceil(node_disk_per_cpu as u32);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let req_mem_mib = req_vcpus * node_mem_per_cpu as u32;
 | 
			
		||||
        let req_disk_mib = req_vcpus * node_disk_per_cpu as u32;
 | 
			
		||||
 | 
			
		||||
        let nano_credits = calculate_nanocredits_for_app(
 | 
			
		||||
            req_vcpus,
 | 
			
		||||
            req_mem_mib,
 | 
			
		||||
            req_disk_mib,
 | 
			
		||||
            self.hours,
 | 
			
		||||
            node.price,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        let resource = Some(AppResource {
 | 
			
		||||
            vcpus: req_vcpus,
 | 
			
		||||
            memory_mib: req_mem_mib,
 | 
			
		||||
            disk_size_mib: req_disk_mib,
 | 
			
		||||
            ports: self.port.clone(),
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        let new_app_req = NewAppReq {
 | 
			
		||||
            node_pubkey: node.node_pubkey.clone(),
 | 
			
		||||
            resource,
 | 
			
		||||
            uuid: "".to_string(),
 | 
			
		||||
            price_per_unit: node.price,
 | 
			
		||||
            locked_nano: nano_credits,
 | 
			
		||||
            app_name: self.app_name.clone(),
 | 
			
		||||
 | 
			
		||||
            ..Default::default()
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        log::debug!(
 | 
			
		||||
            "Node {} can offer the app at {} nanocredits for {} hours. Spec: {} vCPUs, {} MiB mem, {} MiB disk.",
 | 
			
		||||
            node.ip, nano_credits, self.hours, req_vcpus, req_mem_mib, req_disk_mib
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        new_app_req
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,14 +1,15 @@
 | 
			
		||||
// SPDX-License-Identifier: Apache-2.0
 | 
			
		||||
 | 
			
		||||
use detee_shared::app_proto::brain_app_cli_client::BrainAppCliClient;
 | 
			
		||||
use detee_shared::app_proto::{
 | 
			
		||||
    AppContract, AppNodeFilters, AppNodeListResp, DelAppReq, ListAppContractsReq, NewAppReq,
 | 
			
		||||
    NewAppRes,
 | 
			
		||||
};
 | 
			
		||||
use detee_shared::sgx::types::brain::AppDeployConfig;
 | 
			
		||||
use tokio_stream::StreamExt;
 | 
			
		||||
use tonic::transport::Channel;
 | 
			
		||||
 | 
			
		||||
use crate::call_with_follow_redirect;
 | 
			
		||||
use crate::config::Config;
 | 
			
		||||
use crate::sgx::utils::calculate_nanolp_for_app;
 | 
			
		||||
use crate::utils::{self, sign_request};
 | 
			
		||||
 | 
			
		||||
#[derive(thiserror::Error, Debug)]
 | 
			
		||||
@ -25,6 +26,10 @@ pub enum Error {
 | 
			
		||||
    CorruptedRootCa(#[from] std::io::Error),
 | 
			
		||||
    #[error("Internal app error: could not parse Brain URL")]
 | 
			
		||||
    CorruptedBrainUrl,
 | 
			
		||||
    #[error("Max redirects exceeded: {0}")]
 | 
			
		||||
    MaxRedirectsExceeded(String),
 | 
			
		||||
    #[error("Redirect error: {0}")]
 | 
			
		||||
    RedirectError(String),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Result<T> = std::result::Result<T, Error>;
 | 
			
		||||
@ -48,7 +53,7 @@ impl crate::HumanOutput for AppContract {
 | 
			
		||||
            .mapped_ports
 | 
			
		||||
            .clone()
 | 
			
		||||
            .iter()
 | 
			
		||||
            .map(|p| format!("({},{})", p.host_port, p.app_port))
 | 
			
		||||
            .map(|p| format!("({},{})", p.host_port, p.guest_port))
 | 
			
		||||
            .collect::<Vec<_>>()
 | 
			
		||||
            .join(", ");
 | 
			
		||||
        println!(
 | 
			
		||||
@ -57,43 +62,41 @@ impl crate::HumanOutput for AppContract {
 | 
			
		||||
        );
 | 
			
		||||
        println!("The app has mapped ports by the node are: {mapped_ports}");
 | 
			
		||||
        println!(
 | 
			
		||||
            "The App has {} vCPUS, {}MB of memory and a disk of {} MB.",
 | 
			
		||||
            app_resource.vcpu, app_resource.memory_mb, app_resource.disk_mb
 | 
			
		||||
            "The App has {} vCPUS, {}MB of memory and a disk of {} GB.",
 | 
			
		||||
            app_resource.vcpus,
 | 
			
		||||
            app_resource.memory_mib,
 | 
			
		||||
            app_resource.disk_size_mib / 1024
 | 
			
		||||
        );
 | 
			
		||||
        println!("You have locked {} nanoLP in the contract, that get collected at a rate of {} nanoLP per minute.",
 | 
			
		||||
        println!("You have locked {} nanocredits in the contract, that get collected at a rate of {} nanocredits per minute.",
 | 
			
		||||
            self.locked_nano, self.nano_per_minute);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async fn client() -> Result<BrainAppCliClient<Channel>> {
 | 
			
		||||
    Ok(BrainAppCliClient::new(Config::get_brain_channel().await?))
 | 
			
		||||
    let default_brain_url = Config::get_brain_info().0;
 | 
			
		||||
    Ok(BrainAppCliClient::new(Config::connect_brain_channel(default_brain_url).await?))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub async fn new_app(app_deploy_config: AppDeployConfig) -> Result<NewAppRes> {
 | 
			
		||||
    let resource = app_deploy_config.clone().resource;
 | 
			
		||||
    let mut req: NewAppReq = app_deploy_config.clone().into();
 | 
			
		||||
async fn client_from_endpoint(reconnect_endpoint: String) -> Result<BrainAppCliClient<Channel>> {
 | 
			
		||||
    Ok(BrainAppCliClient::new(Config::connect_brain_channel(reconnect_endpoint).await?))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    let locked_nano = calculate_nanolp_for_app(
 | 
			
		||||
        resource.vcpu,
 | 
			
		||||
        resource.memory_mb,
 | 
			
		||||
        resource.disk_mb,
 | 
			
		||||
        app_deploy_config.hours,
 | 
			
		||||
        req.price_per_unit,
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    req.uuid = "".to_string();
 | 
			
		||||
    req.locked_nano = locked_nano;
 | 
			
		||||
    req.admin_pubkey = Config::get_detee_wallet()?;
 | 
			
		||||
    req.hratls_pubkey = Config::get_hratls_pubkey_hex()?;
 | 
			
		||||
 | 
			
		||||
    let res = client().await?.deploy_app(sign_request(req)?).await?;
 | 
			
		||||
    Ok(res.into_inner())
 | 
			
		||||
pub async fn new_app(req: NewAppReq) -> Result<NewAppRes> {
 | 
			
		||||
    let client = client().await?;
 | 
			
		||||
    match call_with_follow_redirect!(client, req, new_app).await {
 | 
			
		||||
        Ok(res) => Ok(res.into_inner()),
 | 
			
		||||
        Err(e) => {
 | 
			
		||||
            log::error!("Failed to create new app: {}", e);
 | 
			
		||||
            Err(e.into())
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub async fn delete_app(app_uuid: String) -> Result<()> {
 | 
			
		||||
    let admin_pubkey = Config::get_detee_wallet()?;
 | 
			
		||||
    let delete_req = DelAppReq { uuid: app_uuid, admin_pubkey };
 | 
			
		||||
    let _ = client().await?.delete_app(sign_request(delete_req)?).await?;
 | 
			
		||||
    let client = client().await?;
 | 
			
		||||
    let _ = call_with_follow_redirect!(client, delete_req, delete_app).await?;
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,25 +1,21 @@
 | 
			
		||||
use detee_sgx::{prelude::*, HRaTlsConfigBuilder};
 | 
			
		||||
use detee_shared::{
 | 
			
		||||
    common_proto::Empty,
 | 
			
		||||
    sgx::{pb::dtpm_proto::DtpmGetConfigRes, types::dtpm::FileEntry},
 | 
			
		||||
};
 | 
			
		||||
// SPDX-License-Identifier: Apache-2.0
 | 
			
		||||
 | 
			
		||||
use detee_sgx::prelude::*;
 | 
			
		||||
use detee_sgx::HRaTlsConfigBuilder;
 | 
			
		||||
use detee_shared::common_proto::Empty;
 | 
			
		||||
use detee_shared::sgx::pb::dtpm_proto::DtpmGetConfigRes;
 | 
			
		||||
use detee_shared::sgx::types::dtpm::FileEntry;
 | 
			
		||||
use hyper_rustls::HttpsConnectorBuilder;
 | 
			
		||||
use rustls::ClientConfig;
 | 
			
		||||
use std::sync::{Arc, RwLock};
 | 
			
		||||
use tokio::sync::mpsc;
 | 
			
		||||
use tokio_stream::wrappers::ReceiverStream;
 | 
			
		||||
use tonic::{
 | 
			
		||||
    codec::CompressionEncoding,
 | 
			
		||||
    transport::{Channel, Endpoint},
 | 
			
		||||
};
 | 
			
		||||
use tonic::codec::CompressionEncoding;
 | 
			
		||||
use tonic::transport::{Channel, Endpoint};
 | 
			
		||||
 | 
			
		||||
use detee_shared::sgx::{
 | 
			
		||||
    pb::dtpm_proto::{
 | 
			
		||||
        dtpm_config_manager_client::DtpmConfigManagerClient, DtpmSetConfigReq,
 | 
			
		||||
        FileEntry as FileEntryPb,
 | 
			
		||||
    },
 | 
			
		||||
    types::dtpm::DtpmConfig,
 | 
			
		||||
};
 | 
			
		||||
use detee_shared::sgx::pb::dtpm_proto::dtpm_config_manager_client::DtpmConfigManagerClient;
 | 
			
		||||
use detee_shared::sgx::pb::dtpm_proto::{DtpmSetConfigReq, FileEntry as FileEntryPb};
 | 
			
		||||
use detee_shared::sgx::types::dtpm::DtpmConfig;
 | 
			
		||||
 | 
			
		||||
use crate::config::Config;
 | 
			
		||||
use crate::sgx::utils::hratls_url_and_mr_enclave_from_app_id;
 | 
			
		||||
@ -42,20 +38,18 @@ pub enum Error {
 | 
			
		||||
 | 
			
		||||
type Result<T> = std::result::Result<T, Error>;
 | 
			
		||||
 | 
			
		||||
pub async fn connect_app_dtpm_client(app_uuid: &str) -> Result<DtpmConfigManagerClient<Channel>> {
 | 
			
		||||
pub async fn dtpm_client(
 | 
			
		||||
    hratls_uri: &str,
 | 
			
		||||
    mr_enclave: &[u8; 32],
 | 
			
		||||
) -> Result<DtpmConfigManagerClient<Channel>> {
 | 
			
		||||
    let private_key_pem = Config::get_hratls_private_key()?;
 | 
			
		||||
 | 
			
		||||
    let (hratls_uri, package_mr_enclave) = hratls_url_and_mr_enclave_from_app_id(app_uuid).await?;
 | 
			
		||||
    log::info!("hratls uri: {}\nmr_enclave: {:?}", &hratls_uri, &package_mr_enclave);
 | 
			
		||||
 | 
			
		||||
    let hratls_config =
 | 
			
		||||
        Arc::new(RwLock::new(HRaTlsConfig::new().with_hratls_private_key_pem(private_key_pem)));
 | 
			
		||||
 | 
			
		||||
    if let Some(mr_enclave) = package_mr_enclave {
 | 
			
		||||
        hratls_config.write().unwrap().allow_more_instance_measurement(
 | 
			
		||||
            InstanceMeasurement::new().with_mrenclaves(vec![mr_enclave]),
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    hratls_config.write().unwrap().allow_more_instance_measurement(
 | 
			
		||||
        InstanceMeasurement::new().with_mrenclaves(vec![*mr_enclave]),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    let client_tls_config = ClientConfig::from_hratls_config(hratls_config.clone())?;
 | 
			
		||||
    let connector = HttpsConnectorBuilder::new()
 | 
			
		||||
@ -64,13 +58,17 @@ pub async fn connect_app_dtpm_client(app_uuid: &str) -> Result<DtpmConfigManager
 | 
			
		||||
        .enable_http2()
 | 
			
		||||
        .build();
 | 
			
		||||
 | 
			
		||||
    let channel = Endpoint::from_shared(hratls_uri)?.connect_with_connector(connector).await?;
 | 
			
		||||
    let channel =
 | 
			
		||||
        Endpoint::from_shared(hratls_uri.to_string())?.connect_with_connector(connector).await?;
 | 
			
		||||
 | 
			
		||||
    Ok(DtpmConfigManagerClient::new(channel).send_compressed(CompressionEncoding::Zstd))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub async fn update_config(app_uuid: &str, config: DtpmConfig) -> Result<()> {
 | 
			
		||||
    let dtpm_client = connect_app_dtpm_client(app_uuid).await?;
 | 
			
		||||
    let (hratls_uri, mr_enclave) = hratls_url_and_mr_enclave_from_app_id(app_uuid).await?;
 | 
			
		||||
    let mr_enclave = mr_enclave.expect("App contract does not have a mr_enclave");
 | 
			
		||||
 | 
			
		||||
    let dtpm_client = dtpm_client(&hratls_uri, &mr_enclave).await?;
 | 
			
		||||
 | 
			
		||||
    upload_files_pb(config.filesystems.clone(), &dtpm_client).await?;
 | 
			
		||||
    let req = DtpmSetConfigReq { config_data: Some(config.into()), ..Default::default() };
 | 
			
		||||
@ -79,7 +77,10 @@ pub async fn update_config(app_uuid: &str, config: DtpmConfig) -> Result<()> {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub async fn get_config(app_uuid: &str) -> Result<DtpmConfig> {
 | 
			
		||||
    let dtpm_client = connect_app_dtpm_client(app_uuid).await?;
 | 
			
		||||
    let (hratls_uri, mr_enclave) = hratls_url_and_mr_enclave_from_app_id(app_uuid).await?;
 | 
			
		||||
    let mr_enclave = mr_enclave.expect("App contract does not have a mr_enclave");
 | 
			
		||||
 | 
			
		||||
    let dtpm_client = dtpm_client(&hratls_uri, &mr_enclave).await?;
 | 
			
		||||
    let config_res = get_config_pb(&dtpm_client).await?;
 | 
			
		||||
    let config: DtpmConfig =
 | 
			
		||||
        config_res.config_data.ok_or(Error::Dtpm("config data not found".to_string()))?.into();
 | 
			
		||||
 | 
			
		||||
@ -1,20 +1,19 @@
 | 
			
		||||
// SPDX-License-Identifier: Apache-2.0
 | 
			
		||||
 | 
			
		||||
pub mod cli_handler;
 | 
			
		||||
pub mod config;
 | 
			
		||||
pub mod deploy;
 | 
			
		||||
pub mod grpc_brain;
 | 
			
		||||
pub mod grpc_dtpm;
 | 
			
		||||
pub mod packaging;
 | 
			
		||||
pub mod utils;
 | 
			
		||||
 | 
			
		||||
use crate::config::Config;
 | 
			
		||||
use crate::snp;
 | 
			
		||||
use crate::utils::shorten_string;
 | 
			
		||||
use crate::{constants::HRATLS_APP_PORT, utils::block_on};
 | 
			
		||||
use detee_shared::{
 | 
			
		||||
    app_proto::{
 | 
			
		||||
        AppContract as AppContractPB, AppNodeFilters, AppNodeListResp, AppResource,
 | 
			
		||||
        ListAppContractsReq, NewAppRes,
 | 
			
		||||
    },
 | 
			
		||||
    sgx::types::brain::Resource,
 | 
			
		||||
use crate::constants::HRATLS_APP_PORT;
 | 
			
		||||
use crate::utils::{block_on, shorten_string};
 | 
			
		||||
use detee_shared::app_proto::{
 | 
			
		||||
    AppContract as AppContractPB, AppNodeFilters, AppNodeListResp, AppResource,
 | 
			
		||||
    ListAppContractsReq, NewAppRes,
 | 
			
		||||
};
 | 
			
		||||
use grpc_brain::get_one_app_node;
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
@ -29,8 +28,18 @@ pub enum Error {
 | 
			
		||||
    AppContractNotFound(String),
 | 
			
		||||
    #[error("Brain returned the following error: {0}")]
 | 
			
		||||
    Brain(#[from] grpc_brain::Error),
 | 
			
		||||
    #[error("Did not find a SGX node that matches your criteria")]
 | 
			
		||||
    NoValidNodeFound,
 | 
			
		||||
    #[error("{0}")]
 | 
			
		||||
    Dtpm(#[from] crate::sgx::grpc_dtpm::Error),
 | 
			
		||||
    #[error("Could not read file from disk: {0}")]
 | 
			
		||||
    FileNotFound(#[from] std::io::Error),
 | 
			
		||||
    #[error("{0}")]
 | 
			
		||||
    Deployment(String),
 | 
			
		||||
    #[error(transparent)]
 | 
			
		||||
    Reqwest(#[from] reqwest::Error),
 | 
			
		||||
    #[error(transparent)]
 | 
			
		||||
    Serde(#[from] serde_yaml::Error),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Tabled, Debug, Serialize, Deserialize)]
 | 
			
		||||
@ -41,12 +50,12 @@ pub struct AppContract {
 | 
			
		||||
    pub uuid: String,
 | 
			
		||||
    pub name: String,
 | 
			
		||||
    #[tabled(rename = "Cores")]
 | 
			
		||||
    pub vcpu: u32,
 | 
			
		||||
    pub vcpus: u32,
 | 
			
		||||
    #[tabled(rename = "Mem (MB)")]
 | 
			
		||||
    pub memory_mb: u32,
 | 
			
		||||
    #[tabled(rename = "Disk (MB)")]
 | 
			
		||||
    pub disk_mb: u32,
 | 
			
		||||
    #[tabled(rename = "LP/h")]
 | 
			
		||||
    pub memory_mib: u32,
 | 
			
		||||
    #[tabled(rename = "Disk (GB)")]
 | 
			
		||||
    pub disk_size_mib: u32,
 | 
			
		||||
    #[tabled(rename = "credits/h")]
 | 
			
		||||
    pub cost_h: String,
 | 
			
		||||
    #[tabled(rename = "time left", display_with = "display_mins")]
 | 
			
		||||
    pub time_left: u64,
 | 
			
		||||
@ -137,22 +146,22 @@ impl From<AppContractPB> for AppContract {
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let AppResource { vcpu, memory_mb, disk_mb, .. } =
 | 
			
		||||
        let AppResource { vcpus, memory_mib, disk_size_mib, .. } =
 | 
			
		||||
            brain_app_contract.resource.unwrap_or_default();
 | 
			
		||||
 | 
			
		||||
        let exposed_host_ports = brain_app_contract
 | 
			
		||||
            .mapped_ports
 | 
			
		||||
            .iter()
 | 
			
		||||
            .map(|port| (port.host_port, port.app_port))
 | 
			
		||||
            .map(|port| (port.host_port, port.guest_port))
 | 
			
		||||
            .collect::<Vec<_>>();
 | 
			
		||||
 | 
			
		||||
        Self {
 | 
			
		||||
            location,
 | 
			
		||||
            uuid: brain_app_contract.uuid,
 | 
			
		||||
            name: brain_app_contract.app_name,
 | 
			
		||||
            vcpu,
 | 
			
		||||
            memory_mb,
 | 
			
		||||
            disk_mb,
 | 
			
		||||
            vcpus,
 | 
			
		||||
            memory_mib,
 | 
			
		||||
            disk_size_mib,
 | 
			
		||||
            cost_h: format!(
 | 
			
		||||
                "{:.4}",
 | 
			
		||||
                (brain_app_contract.nano_per_minute * 60) as f64 / 1_000_000_000.0
 | 
			
		||||
@ -181,7 +190,6 @@ pub async fn get_one_contract(uuid: &str) -> Result<AppContractPB, Error> {
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Serialize, Deserialize)]
 | 
			
		||||
pub struct AppDeployResponse {
 | 
			
		||||
    pub status: String,
 | 
			
		||||
    pub uuid: String,
 | 
			
		||||
    pub name: String,
 | 
			
		||||
    pub node_ip: String,
 | 
			
		||||
@ -198,14 +206,13 @@ impl crate::HumanOutput for AppDeployResponse {
 | 
			
		||||
impl From<(NewAppRes, String)> for AppDeployResponse {
 | 
			
		||||
    fn from((value, name): (NewAppRes, String)) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            status: value.status,
 | 
			
		||||
            uuid: value.uuid,
 | 
			
		||||
            name,
 | 
			
		||||
            node_ip: value.ip_address,
 | 
			
		||||
            hratls_port: value
 | 
			
		||||
                .mapped_ports
 | 
			
		||||
                .iter()
 | 
			
		||||
                .find(|port| port.app_port == HRATLS_APP_PORT)
 | 
			
		||||
                .find(|port| port.guest_port == HRATLS_APP_PORT)
 | 
			
		||||
                .map(|port| port.host_port)
 | 
			
		||||
                .unwrap_or(HRATLS_APP_PORT),
 | 
			
		||||
            error: value.error,
 | 
			
		||||
@ -225,23 +232,6 @@ impl crate::HumanOutput for AppDeleteResponse {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub async fn get_app_node(
 | 
			
		||||
    resource: Resource,
 | 
			
		||||
    location: snp::deploy::Location,
 | 
			
		||||
) -> Result<AppNodeListResp, grpc_brain::Error> {
 | 
			
		||||
    let app_node_filter = AppNodeFilters {
 | 
			
		||||
        vcpus: resource.vcpu,
 | 
			
		||||
        memory_mb: resource.memory_mb,
 | 
			
		||||
        storage_mb: resource.disk_mb,
 | 
			
		||||
        country: location.country.clone().unwrap_or_default(),
 | 
			
		||||
        region: location.region.clone().unwrap_or_default(),
 | 
			
		||||
        city: location.city.clone().unwrap_or_default(),
 | 
			
		||||
        ip: location.node_ip.clone().unwrap_or_default(),
 | 
			
		||||
        node_pubkey: String::new(),
 | 
			
		||||
    };
 | 
			
		||||
    get_one_app_node(app_node_filter).await
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn inspect_node(ip: String) -> Result<AppNodeListResp, grpc_brain::Error> {
 | 
			
		||||
    let req = AppNodeFilters { ip, ..Default::default() };
 | 
			
		||||
    block_on(get_one_app_node(req))
 | 
			
		||||
@ -267,7 +257,7 @@ impl From<AppNodeListResp> for TabledAppNode {
 | 
			
		||||
            operator: brain_node.operator,
 | 
			
		||||
            location: brain_node.city + ", " + &brain_node.region + ", " + &brain_node.country,
 | 
			
		||||
            public_ip: brain_node.ip,
 | 
			
		||||
            price: format!("{} nanoLP/min", brain_node.price),
 | 
			
		||||
            price: format!("{} nanocredits/min", brain_node.price),
 | 
			
		||||
            reports: brain_node.reports.len(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@ -283,10 +273,10 @@ impl super::HumanOutput for Vec<AppNodeListResp> {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn print_nodes() -> Result<Vec<AppNodeListResp>, grpc_brain::Error> {
 | 
			
		||||
pub fn print_nodes() -> Result<Vec<AppNodeListResp>, Error> {
 | 
			
		||||
    log::debug!("This will support flags in the future, but we have only one node atm.");
 | 
			
		||||
    let req = AppNodeFilters { ..Default::default() };
 | 
			
		||||
    block_on(grpc_brain::get_app_node_list(req))
 | 
			
		||||
    Ok(block_on(grpc_brain::get_app_node_list(req))?)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub async fn get_app_node_by_contract(uuid: &str) -> Result<AppNodeListResp, Error> {
 | 
			
		||||
@ -309,7 +299,8 @@ fn write_uuid_list(app_contracts: &[AppContract]) -> Result<(), Error> {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn append_uuid_list(uuid: &str, app_name: &str) -> Result<(), Error> {
 | 
			
		||||
    use std::{fs::OpenOptions, io::prelude::*};
 | 
			
		||||
    use std::fs::OpenOptions;
 | 
			
		||||
    use std::io::prelude::*;
 | 
			
		||||
    let mut file =
 | 
			
		||||
        OpenOptions::new().create(true).append(true).open(Config::app_uuid_list_path()?).unwrap();
 | 
			
		||||
    writeln!(file, "{uuid}\t{app_name}")?;
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,5 @@
 | 
			
		||||
// SPDX-License-Identifier: Apache-2.0
 | 
			
		||||
 | 
			
		||||
use crate::config::Config;
 | 
			
		||||
use std::process::Command;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,33 +1,8 @@
 | 
			
		||||
use crate::constants::HRATLS_APP_PORT;
 | 
			
		||||
use crate::sgx::get_one_contract;
 | 
			
		||||
use crate::sgx::grpc_brain::new_app;
 | 
			
		||||
use crate::sgx::grpc_dtpm::connect_app_dtpm_client;
 | 
			
		||||
use crate::sgx::grpc_dtpm::set_config_pb;
 | 
			
		||||
use crate::sgx::grpc_dtpm::upload_files_pb;
 | 
			
		||||
use crate::sgx::package_entry_from_name;
 | 
			
		||||
use detee_shared::app_proto::NewAppRes;
 | 
			
		||||
use detee_shared::sgx::pb::dtpm_proto::DtpmSetConfigReq;
 | 
			
		||||
use detee_shared::sgx::types::brain::AppDeployConfig;
 | 
			
		||||
use detee_shared::sgx::types::dtpm::DtpmConfig;
 | 
			
		||||
use detee_shared::sgx::types::dtpm::EnvironmentEntry;
 | 
			
		||||
use tokio_retry::strategy::FixedInterval;
 | 
			
		||||
use tokio_retry::Retry;
 | 
			
		||||
// SPDX-License-Identifier: Apache-2.0
 | 
			
		||||
 | 
			
		||||
#[derive(thiserror::Error, Debug)]
 | 
			
		||||
pub enum Error {
 | 
			
		||||
    #[error(transparent)]
 | 
			
		||||
    Reqwest(#[from] reqwest::Error),
 | 
			
		||||
    #[error(transparent)]
 | 
			
		||||
    Serde(#[from] serde_yaml::Error),
 | 
			
		||||
    #[error("{0}")]
 | 
			
		||||
    Package(std::string::String),
 | 
			
		||||
    #[error("{0}")]
 | 
			
		||||
    Brain(#[from] crate::sgx::grpc_brain::Error),
 | 
			
		||||
    #[error("{0}")]
 | 
			
		||||
    Dtpm(#[from] crate::sgx::grpc_dtpm::Error),
 | 
			
		||||
    #[error("{0}")]
 | 
			
		||||
    Deployment(String),
 | 
			
		||||
}
 | 
			
		||||
use crate::constants::HRATLS_APP_PORT;
 | 
			
		||||
use crate::sgx::{get_one_contract, Error};
 | 
			
		||||
use detee_shared::sgx::types::dtpm::{DtpmConfig, EnvironmentEntry};
 | 
			
		||||
 | 
			
		||||
pub async fn hratls_url_and_mr_enclave_from_app_id(
 | 
			
		||||
    app_id: &str,
 | 
			
		||||
@ -48,44 +23,31 @@ pub async fn hratls_url_and_mr_enclave_from_app_id(
 | 
			
		||||
    let dtpm_port = app_contract
 | 
			
		||||
        .mapped_ports
 | 
			
		||||
        .iter()
 | 
			
		||||
        .find(|port| port.app_port == HRATLS_APP_PORT)
 | 
			
		||||
        .find(|port| port.guest_port == HRATLS_APP_PORT)
 | 
			
		||||
        .ok_or(crate::sgx::grpc_dtpm::Error::Dtpm("Could not find DTMP port".to_string()))?
 | 
			
		||||
        .host_port;
 | 
			
		||||
 | 
			
		||||
    Ok((format!("https://{public_ip}:{dtpm_port}"), mr_enclave))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub async fn fetch_config(package_name: &str) -> Result<DtpmConfig, Error> {
 | 
			
		||||
    let index_package_entry = package_entry_from_name(package_name)
 | 
			
		||||
        .ok_or(Error::Package("package not found for ".to_string() + package_name))?;
 | 
			
		||||
 | 
			
		||||
    let launch_config_url = index_package_entry.launch_config_url.clone();
 | 
			
		||||
 | 
			
		||||
    let launch_config_str = reqwest::get(launch_config_url).await?.text().await?;
 | 
			
		||||
 | 
			
		||||
pub async fn fetch_config(url: &str) -> Result<DtpmConfig, Error> {
 | 
			
		||||
    let launch_config_str = reqwest::get(url).await?.text().await?;
 | 
			
		||||
    let launch_config = serde_yaml::from_str::<DtpmConfig>(&launch_config_str)?;
 | 
			
		||||
 | 
			
		||||
    Ok(launch_config)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn calculate_nanolp_for_app(
 | 
			
		||||
pub fn calculate_nanocredits_for_app(
 | 
			
		||||
    vcpus: u32,
 | 
			
		||||
    memory_mb: u32,
 | 
			
		||||
    disk_size_mb: u32,
 | 
			
		||||
    memory_mib: u32,
 | 
			
		||||
    disk_size_mib: u32,
 | 
			
		||||
    hours: u64,
 | 
			
		||||
    node_price: u64,
 | 
			
		||||
) -> u64 {
 | 
			
		||||
    // this calculation needs to match the calculation of the network
 | 
			
		||||
    let total_units =
 | 
			
		||||
        (vcpus as f64 * 5f64) + (memory_mb as f64 / 200f64) + (disk_size_mb as f64 / 10000f64);
 | 
			
		||||
    let total_units = (vcpus as f64 * 5f64)
 | 
			
		||||
        + (memory_mib as f64 / 200f64)
 | 
			
		||||
        + (disk_size_mib as f64 / 1024f64 / 10f64);
 | 
			
		||||
    let locked_nano = (hours as f64 * 60f64 * total_units * node_price as f64) as u64;
 | 
			
		||||
    eprintln!(
 | 
			
		||||
            "Node price: {}/unit/minute. Total Units for hardware requested: {:.4}. Locking {} LP (offering the App for {} hours).",
 | 
			
		||||
            node_price as f64 / 1_000_000_000.0,
 | 
			
		||||
            total_units,
 | 
			
		||||
            locked_nano as f64 / 1_000_000_000.0,
 | 
			
		||||
            hours
 | 
			
		||||
        );
 | 
			
		||||
    locked_nano
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -122,35 +84,3 @@ pub fn override_envs_and_args_launch_config(
 | 
			
		||||
 | 
			
		||||
    launch_config
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub async fn deploy_new_app_and_update_config(
 | 
			
		||||
    app_deploy_config: AppDeployConfig,
 | 
			
		||||
    launch_config: Option<DtpmConfig>,
 | 
			
		||||
) -> Result<NewAppRes, Error> {
 | 
			
		||||
    let new_app_res = new_app(app_deploy_config).await?;
 | 
			
		||||
 | 
			
		||||
    if new_app_res.error.is_empty() {
 | 
			
		||||
        if let Some(launch_config) = launch_config {
 | 
			
		||||
            eprint!("Deploying...");
 | 
			
		||||
            tokio::time::sleep(tokio::time::Duration::from_millis(2500)).await;
 | 
			
		||||
            let dtpm_client = Retry::spawn(FixedInterval::from_millis(1000).take(30), || {
 | 
			
		||||
                log::debug!("retrying attestation and launch config update");
 | 
			
		||||
                eprint!(".");
 | 
			
		||||
                connect_app_dtpm_client(&new_app_res.uuid)
 | 
			
		||||
            })
 | 
			
		||||
            .await?;
 | 
			
		||||
            println!("");
 | 
			
		||||
            upload_files_pb(launch_config.filesystems.clone(), &dtpm_client).await?;
 | 
			
		||||
 | 
			
		||||
            let config_data = Some(launch_config.into());
 | 
			
		||||
            log::trace!("Decoded the configuration... {:?}", config_data);
 | 
			
		||||
            let req = DtpmSetConfigReq { config_data, ..Default::default() };
 | 
			
		||||
            set_config_pb(req, &dtpm_client).await?;
 | 
			
		||||
            Ok(new_app_res)
 | 
			
		||||
        } else {
 | 
			
		||||
            Ok(new_app_res)
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        Err(Error::Deployment(new_app_res.error))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,6 @@
 | 
			
		||||
use crate::general;
 | 
			
		||||
use crate::name_generator;
 | 
			
		||||
use crate::snp;
 | 
			
		||||
use crate::{cli_print, SimpleOutput};
 | 
			
		||||
// SPDX-License-Identifier: Apache-2.0
 | 
			
		||||
 | 
			
		||||
use crate::{cli_print, general, name_generator, snp, SimpleOutput};
 | 
			
		||||
use clap::ArgMatches;
 | 
			
		||||
use std::error::Error;
 | 
			
		||||
 | 
			
		||||
@ -28,7 +27,14 @@ pub fn handle_vm(matches: &ArgMatches) {
 | 
			
		||||
 | 
			
		||||
pub fn handle_vm_nodes(matches: &ArgMatches) {
 | 
			
		||||
    match matches.subcommand() {
 | 
			
		||||
        Some(("search", _)) => cli_print(snp::print_nodes().map_err(Into::into)),
 | 
			
		||||
        Some(("search", arguments)) => {
 | 
			
		||||
            let location = arguments.get_one::<String>("location").unwrap().as_str();
 | 
			
		||||
            cli_print(snp::search_nodes(location.into()).map_err(Into::into));
 | 
			
		||||
        }
 | 
			
		||||
        Some(("offers", arguments)) => {
 | 
			
		||||
            let location = arguments.get_one::<String>("location").unwrap().as_str();
 | 
			
		||||
            cli_print(snp::print_node_offers(location.into()).map_err(Into::into));
 | 
			
		||||
        }
 | 
			
		||||
        Some(("inspect", path_subcommand)) => {
 | 
			
		||||
            let ip: String = path_subcommand.get_one::<String>("ip").unwrap().clone();
 | 
			
		||||
            cli_print(snp::inspect_node(ip).map_err(Into::into));
 | 
			
		||||
@ -67,8 +73,8 @@ fn handle_vm_deploy(matches: &ArgMatches) -> Result<snp::VmSshArgs, Box<dyn Erro
 | 
			
		||||
        ipv4,
 | 
			
		||||
        public_ipv6: false,
 | 
			
		||||
        vcpus: *matches.get_one::<u32>("vcpus").unwrap(),
 | 
			
		||||
        memory_mb: *matches.get_one::<u32>("memory").unwrap(),
 | 
			
		||||
        disk_size_gb: *matches.get_one::<u32>("disk").unwrap(),
 | 
			
		||||
        memory_gib: *matches.get_one::<u32>("memory").unwrap(),
 | 
			
		||||
        disk_size_gib: *matches.get_one::<u32>("disk").unwrap(),
 | 
			
		||||
        dtrfs: None,
 | 
			
		||||
        hours: *matches.get_one::<u32>("hours").unwrap(),
 | 
			
		||||
        price: *matches.get_one::<u64>("price").unwrap(),
 | 
			
		||||
@ -90,10 +96,6 @@ fn handle_vm_update(update_vm_args: &ArgMatches) -> Result<SimpleOutput, Box<dyn
 | 
			
		||||
    let uuid = update_vm_args.get_one::<String>("uuid").unwrap().clone();
 | 
			
		||||
    let hostname = update_vm_args.get_one::<String>("hostname").unwrap().clone();
 | 
			
		||||
    let memory = *update_vm_args.get_one::<u32>("memory").unwrap();
 | 
			
		||||
    if memory > 0 && memory < 800 {
 | 
			
		||||
        log::error!("At least 800MB of memory must be assgined to the VM");
 | 
			
		||||
        return Ok(SimpleOutput::from(""));
 | 
			
		||||
    }
 | 
			
		||||
    snp::update::Request::process_request(
 | 
			
		||||
        hostname,
 | 
			
		||||
        &uuid,
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
use super::{
 | 
			
		||||
    grpc::{self, proto},
 | 
			
		||||
    injector, Distro, Dtrfs, Error, VmSshArgs, DEFAULT_ARCHLINUX, DEFAULT_DTRFS,
 | 
			
		||||
};
 | 
			
		||||
// SPDX-License-Identifier: Apache-2.0
 | 
			
		||||
 | 
			
		||||
use super::grpc::{self, proto};
 | 
			
		||||
use super::{injector, Distro, Dtrfs, Error, VmSshArgs, DEFAULT_ARCHLINUX, DEFAULT_DTRFS};
 | 
			
		||||
use crate::config::Config;
 | 
			
		||||
use crate::utils::block_on;
 | 
			
		||||
use log::{debug, info};
 | 
			
		||||
@ -13,44 +13,18 @@ pub enum IPv4Config {
 | 
			
		||||
    PublicIPv4,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//  TODO: push this out of snp module
 | 
			
		||||
#[derive(Serialize, Deserialize, Default)]
 | 
			
		||||
pub struct Location {
 | 
			
		||||
    pub node_ip: Option<String>,
 | 
			
		||||
    pub country: Option<String>,
 | 
			
		||||
    pub region: Option<String>,
 | 
			
		||||
    pub city: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<&str> for Location {
 | 
			
		||||
    fn from(s: &str) -> Self {
 | 
			
		||||
        match s {
 | 
			
		||||
            "Canada" => Self { country: Some("CA".to_string()), ..Default::default() },
 | 
			
		||||
            "Montreal" => Self { city: Some("Montréal".to_string()), ..Default::default() },
 | 
			
		||||
            "Vancouver" => Self { city: Some("Vancouver".to_string()), ..Default::default() },
 | 
			
		||||
            "US" => Self { country: Some("US".to_string()), ..Default::default() },
 | 
			
		||||
            "California" => Self { country: Some("US".to_string()), ..Default::default() },
 | 
			
		||||
            "France" => Self { country: Some("FR".to_string()), ..Default::default() },
 | 
			
		||||
            "GB" => Self { country: Some("GB".to_string()), ..Default::default() },
 | 
			
		||||
            "Random" => Self { ..Default::default() },
 | 
			
		||||
            "DE" => Self { country: Some("DE".to_string()), ..Default::default() },
 | 
			
		||||
            _ => Self { city: Some("Vancouver".to_string()), ..Default::default() },
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Deserialize)]
 | 
			
		||||
pub struct Request {
 | 
			
		||||
    pub hostname: String,
 | 
			
		||||
    pub hours: u32,
 | 
			
		||||
    // price per unit per minute
 | 
			
		||||
    pub price: u64,
 | 
			
		||||
    pub location: Location,
 | 
			
		||||
    pub location: super::Location,
 | 
			
		||||
    pub ipv4: IPv4Config,
 | 
			
		||||
    pub public_ipv6: bool,
 | 
			
		||||
    pub vcpus: u32,
 | 
			
		||||
    pub memory_mb: u32,
 | 
			
		||||
    pub disk_size_gb: u32,
 | 
			
		||||
    pub memory_gib: u32,
 | 
			
		||||
    pub disk_size_gib: u32,
 | 
			
		||||
    pub dtrfs: Option<Dtrfs>,
 | 
			
		||||
    pub distro: Option<Distro>,
 | 
			
		||||
}
 | 
			
		||||
@ -68,8 +42,8 @@ impl Request {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn deploy(&self) -> Result<VmSshArgs, Error> {
 | 
			
		||||
        let (node_ip, new_vm_resp) = self.send_vm_request()?;
 | 
			
		||||
        info!("Got confirmation from the node {node_ip} that VM started.");
 | 
			
		||||
        let (vcpus, new_vm_resp) = self.calculate_and_send_request()?;
 | 
			
		||||
        info!("Got confirmation from the node that the VM started.");
 | 
			
		||||
        debug!("IPs and ports assigned by node are: {new_vm_resp:#?}");
 | 
			
		||||
        if !new_vm_resp.error.is_empty() {
 | 
			
		||||
            return Err(Error::Node(new_vm_resp.error));
 | 
			
		||||
@ -81,7 +55,7 @@ impl Request {
 | 
			
		||||
        let args = new_vm_resp.args.ok_or(Error::NoMeasurement)?;
 | 
			
		||||
        let measurement_args = injector::Args {
 | 
			
		||||
            uuid: new_vm_resp.uuid.clone(),
 | 
			
		||||
            vcpus: self.vcpus,
 | 
			
		||||
            vcpus,
 | 
			
		||||
            kernel: kernel_sha,
 | 
			
		||||
            initrd: dtrfs_sha,
 | 
			
		||||
            args: args.clone(),
 | 
			
		||||
@ -105,10 +79,106 @@ impl Request {
 | 
			
		||||
        Ok(ssh_args)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // returns node IP and data regarding the new VM
 | 
			
		||||
    fn send_vm_request(&self) -> Result<(String, proto::NewVmResp), Error> {
 | 
			
		||||
        let admin_pubkey = Config::get_detee_wallet()?;
 | 
			
		||||
        let node = self.get_node()?;
 | 
			
		||||
    /// returns number of vCPUs and response from the daemon
 | 
			
		||||
    fn calculate_and_send_request(&self) -> Result<(u32, proto::NewVmResp), Error> {
 | 
			
		||||
        let new_vm_req = self.get_cheapest_offer()?;
 | 
			
		||||
        let vcpus = new_vm_req.vcpus;
 | 
			
		||||
 | 
			
		||||
        eprintln!(
 | 
			
		||||
            "Locking {} credits for {} hours of the following HW spec: {} vCPUs, {} MiB Mem, {} MiB Disk",
 | 
			
		||||
            new_vm_req.locked_nano as f64 / 1_000_000_000_f64,
 | 
			
		||||
            self.hours,
 | 
			
		||||
            new_vm_req.vcpus,
 | 
			
		||||
            new_vm_req.memory_mib,
 | 
			
		||||
            new_vm_req.disk_size_mib
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // eprint!(
 | 
			
		||||
        //     "Node price: {}/unit/minute. Total Units for hardware requested: {}. ",
 | 
			
		||||
        //     node_price as f64 / 1_000_000_000.0,
 | 
			
		||||
        //     total_units,
 | 
			
		||||
        // );
 | 
			
		||||
        // eprintln!(
 | 
			
		||||
        //     "Locking {} LP (offering the VM for {} hours).",
 | 
			
		||||
        //     locked_nano as f64 / 1_000_000_000.0,
 | 
			
		||||
        //     hours
 | 
			
		||||
        // );
 | 
			
		||||
 | 
			
		||||
        let new_vm_resp = block_on(grpc::create_vm(new_vm_req))?;
 | 
			
		||||
        if !new_vm_resp.error.is_empty() {
 | 
			
		||||
            return Err(Error::Node(new_vm_resp.error));
 | 
			
		||||
        }
 | 
			
		||||
        Ok((vcpus, new_vm_resp))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn get_cheapest_offer(&self) -> Result<proto::NewVmReq, Error> {
 | 
			
		||||
        let (free_ports, offers_ipv4) = match &self.ipv4 {
 | 
			
		||||
            IPv4Config::PublishPorts(vec) => (vec.len() as u32, false),
 | 
			
		||||
            IPv4Config::PublicIPv4 => (0, true),
 | 
			
		||||
        };
 | 
			
		||||
        let filters = proto::VmNodeFilters {
 | 
			
		||||
            free_ports,
 | 
			
		||||
            offers_ipv4,
 | 
			
		||||
            offers_ipv6: self.public_ipv6,
 | 
			
		||||
            vcpus: self.vcpus,
 | 
			
		||||
            memory_mib: self.memory_gib * 1024,
 | 
			
		||||
            storage_mib: self.disk_size_gib * 1024,
 | 
			
		||||
            country: self.location.country.clone().unwrap_or_default(),
 | 
			
		||||
            region: self.location.region.clone().unwrap_or_default(),
 | 
			
		||||
            city: self.location.city.clone().unwrap_or_default(),
 | 
			
		||||
            ip: self.location.node_ip.clone().unwrap_or_default(),
 | 
			
		||||
            node_pubkey: String::new(),
 | 
			
		||||
        };
 | 
			
		||||
        let node_list = match block_on(grpc::get_node_list(filters)) {
 | 
			
		||||
            Ok(node_list) => Ok(node_list),
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                log::error!("Coult not get node from brain: {e:?}");
 | 
			
		||||
                Err(Error::NoValidNodeFound)
 | 
			
		||||
            }
 | 
			
		||||
        }?;
 | 
			
		||||
 | 
			
		||||
        let mut node_list_iter = node_list.iter();
 | 
			
		||||
        let mut final_request = self.calculate_vm_request(
 | 
			
		||||
            Config::get_detee_wallet()?,
 | 
			
		||||
            node_list_iter.next().ok_or(Error::NoValidNodeFound)?,
 | 
			
		||||
        );
 | 
			
		||||
        while let Some(node) = node_list_iter.next() {
 | 
			
		||||
            let new_vm_req = self.calculate_vm_request(Config::get_detee_wallet()?, node);
 | 
			
		||||
            if new_vm_req.locked_nano < final_request.locked_nano {
 | 
			
		||||
                final_request = new_vm_req;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Ok(final_request)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn calculate_vm_request(
 | 
			
		||||
        &self,
 | 
			
		||||
        admin_pubkey: String,
 | 
			
		||||
        node: &proto::VmNodeListResp,
 | 
			
		||||
    ) -> proto::NewVmReq {
 | 
			
		||||
        let memory_per_cpu = node.memory_mib / node.vcpus;
 | 
			
		||||
        let disk_per_cpu = node.disk_mib / node.vcpus;
 | 
			
		||||
        let mut vcpus = self.vcpus;
 | 
			
		||||
        if vcpus < (self.memory_gib * 1024).div_ceil(memory_per_cpu as u32) {
 | 
			
		||||
            vcpus = (self.memory_gib * 1024).div_ceil(memory_per_cpu as u32);
 | 
			
		||||
        }
 | 
			
		||||
        if vcpus < (self.disk_size_gib * 1024).div_ceil(disk_per_cpu as u32) {
 | 
			
		||||
            vcpus = (self.disk_size_gib * 1024).div_ceil(disk_per_cpu as u32);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let memory_mib = vcpus * memory_per_cpu as u32;
 | 
			
		||||
        let disk_size_mib = vcpus * disk_per_cpu as u32;
 | 
			
		||||
 | 
			
		||||
        let nanocredits = super::calculate_nanocredits(
 | 
			
		||||
            vcpus,
 | 
			
		||||
            memory_mib,
 | 
			
		||||
            disk_size_mib,
 | 
			
		||||
            node.public_ipv4,
 | 
			
		||||
            self.hours,
 | 
			
		||||
            node.price,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        let (extra_ports, public_ipv4): (Vec<u32>, bool) = match &self.ipv4 {
 | 
			
		||||
            IPv4Config::PublishPorts(vec) => (vec.to_vec(), false),
 | 
			
		||||
            IPv4Config::PublicIPv4 => (Vec::new(), true),
 | 
			
		||||
@ -122,63 +192,31 @@ impl Request {
 | 
			
		||||
                DEFAULT_DTRFS.dtrfs_sha.clone(),
 | 
			
		||||
            ),
 | 
			
		||||
        };
 | 
			
		||||
        let locked_nano = super::calculate_nanolp(
 | 
			
		||||
            self.vcpus,
 | 
			
		||||
            self.memory_mb,
 | 
			
		||||
            self.disk_size_gb,
 | 
			
		||||
            public_ipv4,
 | 
			
		||||
            self.hours,
 | 
			
		||||
            self.price,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        let brain_req = proto::NewVmReq {
 | 
			
		||||
            uuid: String::new(),
 | 
			
		||||
            hostname: self.hostname.clone(),
 | 
			
		||||
            admin_pubkey,
 | 
			
		||||
            node_pubkey: node.node_pubkey,
 | 
			
		||||
            node_pubkey: node.node_pubkey.clone(),
 | 
			
		||||
            extra_ports,
 | 
			
		||||
            public_ipv4,
 | 
			
		||||
            public_ipv6: self.public_ipv6,
 | 
			
		||||
            disk_size_gb: self.disk_size_gb,
 | 
			
		||||
            vcpus: self.vcpus,
 | 
			
		||||
            memory_mb: self.memory_mb,
 | 
			
		||||
            disk_size_mib,
 | 
			
		||||
            vcpus,
 | 
			
		||||
            memory_mib,
 | 
			
		||||
            kernel_url,
 | 
			
		||||
            kernel_sha,
 | 
			
		||||
            dtrfs_url,
 | 
			
		||||
            dtrfs_sha,
 | 
			
		||||
            price_per_unit: self.price,
 | 
			
		||||
            locked_nano,
 | 
			
		||||
            price_per_unit: node.price,
 | 
			
		||||
            locked_nano: nanocredits,
 | 
			
		||||
        };
 | 
			
		||||
        let new_vm_resp = block_on(grpc::create_vm(brain_req))?;
 | 
			
		||||
        if !new_vm_resp.error.is_empty() {
 | 
			
		||||
            return Err(Error::Node(new_vm_resp.error));
 | 
			
		||||
        }
 | 
			
		||||
        Ok((node.ip, new_vm_resp))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn get_node(&self) -> Result<proto::VmNodeListResp, Error> {
 | 
			
		||||
        let (free_ports, offers_ipv4) = match &self.ipv4 {
 | 
			
		||||
            IPv4Config::PublishPorts(vec) => (vec.len() as u32, false),
 | 
			
		||||
            IPv4Config::PublicIPv4 => (0, true),
 | 
			
		||||
        };
 | 
			
		||||
        let filters = proto::VmNodeFilters {
 | 
			
		||||
            free_ports,
 | 
			
		||||
            offers_ipv4,
 | 
			
		||||
            offers_ipv6: self.public_ipv6,
 | 
			
		||||
            vcpus: self.vcpus,
 | 
			
		||||
            memory_mb: self.memory_mb,
 | 
			
		||||
            storage_gb: self.disk_size_gb,
 | 
			
		||||
            country: self.location.country.clone().unwrap_or_default(),
 | 
			
		||||
            region: self.location.region.clone().unwrap_or_default(),
 | 
			
		||||
            city: self.location.city.clone().unwrap_or_default(),
 | 
			
		||||
            ip: self.location.node_ip.clone().unwrap_or_default(),
 | 
			
		||||
            node_pubkey: String::new(),
 | 
			
		||||
        };
 | 
			
		||||
        match block_on(grpc::get_one_node(filters)) {
 | 
			
		||||
            Ok(node) => Ok(node),
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                log::error!("Coult not get node from brain: {e:?}");
 | 
			
		||||
                Err(Error::NoValidNodeFound)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        debug!(
 | 
			
		||||
            "Node {} can offer the VM at {} nanocredits for {} hours. Spec: {} vCPUs, {} MiB mem, {} MiB disk.",
 | 
			
		||||
            node.ip, brain_req.locked_nano, self.hours, brain_req.vcpus, brain_req.memory_mib, brain_req.disk_size_mib
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        brain_req
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,20 +1,23 @@
 | 
			
		||||
// SPDX-License-Identifier: Apache-2.0
 | 
			
		||||
 | 
			
		||||
pub mod proto {
 | 
			
		||||
    pub use detee_shared::general_proto::*;
 | 
			
		||||
    pub use detee_shared::vm_proto::*;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
use crate::call_with_follow_redirect;
 | 
			
		||||
use crate::config::Config;
 | 
			
		||||
use crate::utils::{self, sign_request};
 | 
			
		||||
use lazy_static::lazy_static;
 | 
			
		||||
use log::{debug, info, warn};
 | 
			
		||||
use proto::brain_vm_cli_client::BrainVmCliClient;
 | 
			
		||||
use proto::{
 | 
			
		||||
    brain_vm_cli_client::BrainVmCliClient, DeleteVmReq, ExtendVmReq, ListVmContractsReq, NewVmReq,
 | 
			
		||||
    NewVmResp, UpdateVmReq, UpdateVmResp, VmContract, VmNodeFilters, VmNodeListResp,
 | 
			
		||||
    DeleteVmReq, ExtendVmReq, ListVmContractsReq, NewVmReq, NewVmResp, UpdateVmReq, UpdateVmResp,
 | 
			
		||||
    VmContract, VmNodeFilters, VmNodeListResp,
 | 
			
		||||
};
 | 
			
		||||
use tokio_stream::StreamExt;
 | 
			
		||||
use tonic::metadata::errors::InvalidMetadataValue;
 | 
			
		||||
use tonic::metadata::AsciiMetadataValue;
 | 
			
		||||
use tonic::transport::Channel;
 | 
			
		||||
use tonic::Request;
 | 
			
		||||
 | 
			
		||||
lazy_static! {
 | 
			
		||||
    static ref SECURE_PUBLIC_KEY: String = use_default_string();
 | 
			
		||||
@ -41,6 +44,12 @@ pub enum Error {
 | 
			
		||||
    CorruptedRootCa(#[from] std::io::Error),
 | 
			
		||||
    #[error("Internal app error: could not parse Brain URL")]
 | 
			
		||||
    CorruptedBrainUrl,
 | 
			
		||||
    #[error("Max redirects exceeded: {0}")]
 | 
			
		||||
    MaxRedirectsExceeded(String),
 | 
			
		||||
    #[error("Redirect error: {0}")]
 | 
			
		||||
    RedirectError(String),
 | 
			
		||||
    #[error(transparent)]
 | 
			
		||||
    InternalError(#[from] utils::Error),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl crate::HumanOutput for VmContract {
 | 
			
		||||
@ -49,24 +58,24 @@ impl crate::HumanOutput for VmContract {
 | 
			
		||||
            "The VM {} has the UUID {}, and it runs on the node {}",
 | 
			
		||||
            self.hostname, self.uuid, self.node_pubkey
 | 
			
		||||
        );
 | 
			
		||||
        if self.public_ipv4.is_empty() {
 | 
			
		||||
        if self.vm_public_ipv4.is_empty() {
 | 
			
		||||
            println!(
 | 
			
		||||
                "The VM has no public IPv4. The ports published by the VM are: {:?}",
 | 
			
		||||
                self.exposed_ports
 | 
			
		||||
                "The VM has no public IPv4. The ports mapped from the host to the VM are: {:?}",
 | 
			
		||||
                self.mapped_ports
 | 
			
		||||
            );
 | 
			
		||||
        } else {
 | 
			
		||||
            println!("The Public IPv4 address of the VM is: {}", self.public_ipv4);
 | 
			
		||||
            println!("The Public IPv4 address of the VM is: {}", self.vm_public_ipv4);
 | 
			
		||||
        }
 | 
			
		||||
        if self.public_ipv6.is_empty() {
 | 
			
		||||
        if self.vm_public_ipv6.is_empty() {
 | 
			
		||||
            println!("The VM does not have a public IPv6 address.");
 | 
			
		||||
        } else {
 | 
			
		||||
            println!("The Public IPv6 address of the VM is: {}", self.public_ipv6);
 | 
			
		||||
            println!("The Public IPv6 address of the VM is: {}", self.vm_public_ipv6);
 | 
			
		||||
        }
 | 
			
		||||
        println!(
 | 
			
		||||
            "The VM has {} vCPUS, {}MB of memory and a disk of {} GB.",
 | 
			
		||||
            self.vcpus, self.memory_mb, self.disk_size_gb
 | 
			
		||||
        );
 | 
			
		||||
        println!("You have locked {} nanoLP in the contract, that get collected at a rate of {} nanoLP per minute.",
 | 
			
		||||
        println!("You have locked {} nanocredits in the contract, that get collected at a rate of {} nanocredits per minute.",
 | 
			
		||||
            self.locked_nano, self.nano_per_minute);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -84,21 +93,15 @@ impl crate::HumanOutput for VmNodeListResp {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async fn client() -> Result<BrainVmCliClient<Channel>, Error> {
 | 
			
		||||
    Ok(BrainVmCliClient::new(Config::get_brain_channel().await?))
 | 
			
		||||
    let default_brain_url = Config::get_brain_info().0;
 | 
			
		||||
    info!("brain_url: {default_brain_url}");
 | 
			
		||||
    Ok(BrainVmCliClient::new(Config::connect_brain_channel(default_brain_url).await?))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn sign_request<T: std::fmt::Debug>(req: T) -> Result<Request<T>, Error> {
 | 
			
		||||
    let pubkey = Config::get_detee_wallet()?;
 | 
			
		||||
    let timestamp = chrono::Utc::now().to_rfc3339();
 | 
			
		||||
    let signature = Config::try_sign_message(&format!("{timestamp}{req:?}"))?;
 | 
			
		||||
    let timestamp: AsciiMetadataValue = timestamp.parse()?;
 | 
			
		||||
    let pubkey: AsciiMetadataValue = pubkey.parse()?;
 | 
			
		||||
    let signature: AsciiMetadataValue = signature.parse()?;
 | 
			
		||||
    let mut req = Request::new(req);
 | 
			
		||||
    req.metadata_mut().insert("timestamp", timestamp);
 | 
			
		||||
    req.metadata_mut().insert("pubkey", pubkey);
 | 
			
		||||
    req.metadata_mut().insert("request-signature", signature);
 | 
			
		||||
    Ok(req)
 | 
			
		||||
async fn client_from_endpoint(
 | 
			
		||||
    reconnect_endpoint: String,
 | 
			
		||||
) -> Result<BrainVmCliClient<Channel>, Error> {
 | 
			
		||||
    Ok(BrainVmCliClient::new(Config::connect_brain_channel(reconnect_endpoint).await?))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub async fn get_node_list(req: VmNodeFilters) -> Result<Vec<VmNodeListResp>, Error> {
 | 
			
		||||
@ -128,9 +131,10 @@ pub async fn get_one_node(req: VmNodeFilters) -> Result<VmNodeListResp, Error> {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub async fn create_vm(req: NewVmReq) -> Result<NewVmResp, Error> {
 | 
			
		||||
    let mut client = client().await?;
 | 
			
		||||
    debug!("Sending NewVmReq to brain: {req:?}");
 | 
			
		||||
    match client.new_vm(sign_request(req)?).await {
 | 
			
		||||
 | 
			
		||||
    let client = client().await?;
 | 
			
		||||
    match call_with_follow_redirect!(client, req, new_vm).await {
 | 
			
		||||
        Ok(resp) => Ok(resp.into_inner()),
 | 
			
		||||
        Err(e) => Err(e.into()),
 | 
			
		||||
    }
 | 
			
		||||
@ -157,10 +161,9 @@ pub async fn list_contracts(req: ListVmContractsReq) -> Result<Vec<VmContract>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub async fn delete_vm(uuid: &str) -> Result<(), Error> {
 | 
			
		||||
    let mut client = client().await?;
 | 
			
		||||
    let client = client().await?;
 | 
			
		||||
    let req = DeleteVmReq { uuid: uuid.to_string(), admin_pubkey: Config::get_detee_wallet()? };
 | 
			
		||||
    let result = client.delete_vm(sign_request(req)?).await;
 | 
			
		||||
    match result {
 | 
			
		||||
    match call_with_follow_redirect!(client, req, delete_vm).await {
 | 
			
		||||
        Ok(confirmation) => {
 | 
			
		||||
            log::debug!("VM deletion confirmation: {confirmation:?}");
 | 
			
		||||
        }
 | 
			
		||||
@ -180,7 +183,7 @@ pub async fn extend_vm(uuid: String, admin_pubkey: String, locked_nano: u64) ->
 | 
			
		||||
        Ok(confirmation) => {
 | 
			
		||||
            log::debug!("VM contract extension confirmation: {confirmation:?}");
 | 
			
		||||
            log::info!(
 | 
			
		||||
                "VM contract got updated. It now has {} LP locked for the VM.",
 | 
			
		||||
                "VM contract got updated. It now has {} credits locked for the VM.",
 | 
			
		||||
                locked_nano as f64 / 1_000_000_000.0
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
@ -195,9 +198,8 @@ pub async fn extend_vm(uuid: String, admin_pubkey: String, locked_nano: u64) ->
 | 
			
		||||
 | 
			
		||||
pub async fn update_vm(req: UpdateVmReq) -> Result<UpdateVmResp, Error> {
 | 
			
		||||
    info!("Updating VM {req:?}");
 | 
			
		||||
    let mut client = client().await?;
 | 
			
		||||
    let result = client.update_vm(sign_request(req)?).await;
 | 
			
		||||
    match result {
 | 
			
		||||
    let client = client().await?;
 | 
			
		||||
    match call_with_follow_redirect!(client, req, update_vm).await {
 | 
			
		||||
        Ok(resp) => {
 | 
			
		||||
            let resp = resp.into_inner();
 | 
			
		||||
            if resp.error.is_empty() {
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,7 @@
 | 
			
		||||
use crate::{config::Config, snp::grpc::proto};
 | 
			
		||||
// SPDX-License-Identifier: Apache-2.0
 | 
			
		||||
 | 
			
		||||
use crate::config::Config;
 | 
			
		||||
use crate::snp::grpc::proto;
 | 
			
		||||
use log::debug;
 | 
			
		||||
use std::net::IpAddr;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										233
									
								
								src/snp/mod.rs
									
									
									
									
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										233
									
								
								src/snp/mod.rs
									
									
									
									
									
								
							@ -1,15 +1,14 @@
 | 
			
		||||
// SPDX-License-Identifier: Apache-2.0
 | 
			
		||||
 | 
			
		||||
pub mod cli_handler;
 | 
			
		||||
pub mod deploy;
 | 
			
		||||
pub mod grpc;
 | 
			
		||||
mod injector;
 | 
			
		||||
pub mod update;
 | 
			
		||||
 | 
			
		||||
use crate::utils::block_on;
 | 
			
		||||
use crate::utils::shorten_string;
 | 
			
		||||
use crate::{
 | 
			
		||||
    config::{self, Config},
 | 
			
		||||
    snp,
 | 
			
		||||
};
 | 
			
		||||
use crate::config::{self, Config};
 | 
			
		||||
use crate::snp;
 | 
			
		||||
use crate::utils::{block_on, display_mib_or_gib, shorten_string};
 | 
			
		||||
use grpc::proto;
 | 
			
		||||
use lazy_static::lazy_static;
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
@ -37,6 +36,32 @@ pub enum Error {
 | 
			
		||||
    Injector(#[from] injector::Error),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//  TODO: push this out of snp module
 | 
			
		||||
#[derive(Serialize, Deserialize, Default)]
 | 
			
		||||
pub struct Location {
 | 
			
		||||
    pub node_ip: Option<String>,
 | 
			
		||||
    pub country: Option<String>,
 | 
			
		||||
    pub region: Option<String>,
 | 
			
		||||
    pub city: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<&str> for Location {
 | 
			
		||||
    fn from(s: &str) -> Self {
 | 
			
		||||
        match s {
 | 
			
		||||
            "Canada" => Self { country: Some("CA".to_string()), ..Default::default() },
 | 
			
		||||
            "Montreal" => Self { city: Some("Montréal".to_string()), ..Default::default() },
 | 
			
		||||
            "Vancouver" => Self { city: Some("Vancouver".to_string()), ..Default::default() },
 | 
			
		||||
            "US" => Self { country: Some("US".to_string()), ..Default::default() },
 | 
			
		||||
            "California" => Self { country: Some("US".to_string()), ..Default::default() },
 | 
			
		||||
            "France" => Self { country: Some("FR".to_string()), ..Default::default() },
 | 
			
		||||
            "GB" => Self { country: Some("GB".to_string()), ..Default::default() },
 | 
			
		||||
            "DE" => Self { country: Some("DE".to_string()), ..Default::default() },
 | 
			
		||||
            "Any" => Self { ..Default::default() },
 | 
			
		||||
            _ => Self { ..Default::default() },
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize, Default)]
 | 
			
		||||
pub struct VmSshArgs {
 | 
			
		||||
    uuid: String,
 | 
			
		||||
@ -64,20 +89,16 @@ impl TryFrom<grpc::proto::VmContract> for VmSshArgs {
 | 
			
		||||
        args.user = "root".to_string();
 | 
			
		||||
        args.key_path =
 | 
			
		||||
            Config::init_config().get_ssh_pubkey()?.trim_end_matches(".pub").to_string();
 | 
			
		||||
        if !contract.public_ipv4.is_empty() {
 | 
			
		||||
            args.ip = contract.public_ipv4;
 | 
			
		||||
        if !contract.vm_public_ipv4.is_empty() {
 | 
			
		||||
            args.ip = contract.vm_public_ipv4;
 | 
			
		||||
            args.port = "22".to_string();
 | 
			
		||||
        } else {
 | 
			
		||||
            args.port = contract.exposed_ports[0].to_string();
 | 
			
		||||
            log::info!(
 | 
			
		||||
                "This VM does not have a public IP. Getting node IP for node {}",
 | 
			
		||||
                contract.node_pubkey
 | 
			
		||||
            args.port = contract.mapped_ports[0].host_port.to_string();
 | 
			
		||||
            log::debug!(
 | 
			
		||||
                "This VM does not have a public IP. Using node public IP: {}",
 | 
			
		||||
                contract.node_ip
 | 
			
		||||
            );
 | 
			
		||||
            let node = block_on(snp::grpc::get_one_node(proto::VmNodeFilters {
 | 
			
		||||
                node_pubkey: contract.node_pubkey.clone(),
 | 
			
		||||
                ..Default::default()
 | 
			
		||||
            }))?;
 | 
			
		||||
            args.ip = node.ip;
 | 
			
		||||
            args.ip = contract.node_ip;
 | 
			
		||||
        }
 | 
			
		||||
        Ok(args)
 | 
			
		||||
    }
 | 
			
		||||
@ -97,12 +118,6 @@ pub struct Dtrfs {
 | 
			
		||||
 | 
			
		||||
impl Dtrfs {
 | 
			
		||||
    pub fn print_dtrfs_list() -> Vec<Self> {
 | 
			
		||||
        // let mut dtrfs_vec = Vec::new();
 | 
			
		||||
        // dtrfs_vec.push(DEFAULT_DTRFS.clone());
 | 
			
		||||
        // dtrfs_vec.push(ALTERNATIVE_INIT[0].clone());
 | 
			
		||||
        // dtrfs_vec.push(ALTERNATIVE_INIT[1].clone());
 | 
			
		||||
        // dtrfs_vec
 | 
			
		||||
 | 
			
		||||
        vec![DEFAULT_DTRFS.clone(), ALTERNATIVE_INIT[0].clone(), ALTERNATIVE_INIT[1].clone()]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -134,15 +149,6 @@ impl super::HumanOutput for Vec<Distro> {
 | 
			
		||||
 | 
			
		||||
impl Distro {
 | 
			
		||||
    pub fn get_template_list() -> Vec<Self> {
 | 
			
		||||
        // let mut distro_vec = Vec::new();
 | 
			
		||||
        // distro_vec.push(DEFAULT_ARCHLINUX.clone());
 | 
			
		||||
        // distro_vec.push(DEFAULT_UBUNTU.clone());
 | 
			
		||||
        // distro_vec.push(DEFAULT_FEDORA.clone());
 | 
			
		||||
        // distro_vec.push(ALTERNATIVE_DISTROS[0].clone());
 | 
			
		||||
        // distro_vec.push(ALTERNATIVE_DISTROS[1].clone());
 | 
			
		||||
        // distro_vec.push(ALTERNATIVE_DISTROS[2].clone());
 | 
			
		||||
        // distro_vec
 | 
			
		||||
 | 
			
		||||
        vec![
 | 
			
		||||
            DEFAULT_ARCHLINUX.clone(),
 | 
			
		||||
            DEFAULT_UBUNTU.clone(),
 | 
			
		||||
@ -174,12 +180,12 @@ pub struct VmContract {
 | 
			
		||||
    pub uuid: String,
 | 
			
		||||
    pub hostname: String,
 | 
			
		||||
    #[tabled(rename = "Cores")]
 | 
			
		||||
    pub vcpus: u32,
 | 
			
		||||
    #[tabled(rename = "Mem (MB)")]
 | 
			
		||||
    pub mem: u32,
 | 
			
		||||
    #[tabled(rename = "Disk")]
 | 
			
		||||
    pub disk: u32,
 | 
			
		||||
    #[tabled(rename = "LP/h")]
 | 
			
		||||
    pub vcpus: u64,
 | 
			
		||||
    #[tabled(rename = "Mem", display_with = "display_mib_or_gib")]
 | 
			
		||||
    pub mem: u64,
 | 
			
		||||
    #[tabled(rename = "Disk", display_with = "display_mib_or_gib")]
 | 
			
		||||
    pub disk: u64,
 | 
			
		||||
    #[tabled(rename = "credits/h")]
 | 
			
		||||
    pub cost_h: f64,
 | 
			
		||||
    #[tabled(rename = "time left", display_with = "display_mins")]
 | 
			
		||||
    pub time_left: u64,
 | 
			
		||||
@ -203,25 +209,13 @@ fn display_mins(minutes: &u64) -> String {
 | 
			
		||||
 | 
			
		||||
impl From<proto::VmContract> for VmContract {
 | 
			
		||||
    fn from(brain_contract: proto::VmContract) -> Self {
 | 
			
		||||
        let node_pubkey = brain_contract.node_pubkey.clone();
 | 
			
		||||
        let location = match block_on(snp::grpc::get_one_node(proto::VmNodeFilters {
 | 
			
		||||
            node_pubkey: node_pubkey.clone(),
 | 
			
		||||
            ..Default::default()
 | 
			
		||||
        })) {
 | 
			
		||||
            Ok(node) => format!("{}, {} ({})", node.city, node.region, node.country),
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                log::warn!("Could not get information about node {node_pubkey} fram brain: {e:?}");
 | 
			
		||||
                String::new()
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        Self {
 | 
			
		||||
            uuid: brain_contract.uuid,
 | 
			
		||||
            hostname: brain_contract.hostname,
 | 
			
		||||
            vcpus: brain_contract.vcpus,
 | 
			
		||||
            mem: brain_contract.memory_mb,
 | 
			
		||||
            disk: brain_contract.disk_size_gb,
 | 
			
		||||
            location,
 | 
			
		||||
            vcpus: brain_contract.vcpus as u64,
 | 
			
		||||
            mem: brain_contract.memory_mb as u64,
 | 
			
		||||
            disk: brain_contract.disk_size_gb as u64,
 | 
			
		||||
            location: brain_contract.location,
 | 
			
		||||
            cost_h: (brain_contract.nano_per_minute * 60) as f64 / 1_000_000_000.0,
 | 
			
		||||
            time_left: brain_contract.locked_nano / brain_contract.nano_per_minute,
 | 
			
		||||
        }
 | 
			
		||||
@ -230,12 +224,22 @@ impl From<proto::VmContract> for VmContract {
 | 
			
		||||
 | 
			
		||||
#[derive(Tabled, Debug, Serialize, Deserialize)]
 | 
			
		||||
pub struct TabledVmNode {
 | 
			
		||||
    #[tabled(rename = "Operator")]
 | 
			
		||||
    #[tabled(rename = "Operator", display_with = "shorten_string")]
 | 
			
		||||
    pub operator: String,
 | 
			
		||||
    #[tabled(rename = "Main IP")]
 | 
			
		||||
    pub main_ip: String,
 | 
			
		||||
    #[tabled(rename = "City, Region, Country")]
 | 
			
		||||
    pub location: String,
 | 
			
		||||
    #[tabled(rename = "IP")]
 | 
			
		||||
    pub public_ip: String,
 | 
			
		||||
    #[tabled(rename = "Cores")]
 | 
			
		||||
    pub vcpus: u64,
 | 
			
		||||
    #[tabled(rename = "Mem", display_with = "display_mib_or_gib")]
 | 
			
		||||
    pub memory_mib: u64,
 | 
			
		||||
    #[tabled(rename = "Disk", display_with = "display_mib_or_gib")]
 | 
			
		||||
    pub disk_mib: u64,
 | 
			
		||||
    #[tabled(rename = "Extra IPv4", display_with = "display_ip_support")]
 | 
			
		||||
    pub public_ipv4: bool,
 | 
			
		||||
    #[tabled(rename = "IPv6", display_with = "display_ip_support")]
 | 
			
		||||
    pub public_ipv6: bool,
 | 
			
		||||
    #[tabled(rename = "Price per unit")]
 | 
			
		||||
    pub price: String,
 | 
			
		||||
    #[tabled(rename = "Reports")]
 | 
			
		||||
@ -247,9 +251,14 @@ impl From<proto::VmNodeListResp> for TabledVmNode {
 | 
			
		||||
        Self {
 | 
			
		||||
            operator: brain_node.operator,
 | 
			
		||||
            location: brain_node.city + ", " + &brain_node.region + ", " + &brain_node.country,
 | 
			
		||||
            public_ip: brain_node.ip,
 | 
			
		||||
            price: format!("{} nanoLP/min", brain_node.price),
 | 
			
		||||
            main_ip: brain_node.ip,
 | 
			
		||||
            price: format!("{} nano/min", brain_node.price),
 | 
			
		||||
            reports: brain_node.reports.len(),
 | 
			
		||||
            vcpus: brain_node.vcpus,
 | 
			
		||||
            memory_mib: brain_node.memory_mib,
 | 
			
		||||
            disk_mib: brain_node.disk_mib,
 | 
			
		||||
            public_ipv4: brain_node.public_ipv4,
 | 
			
		||||
            public_ipv6: brain_node.public_ipv6,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -341,7 +350,8 @@ fn write_uuid_list(contracts: &[VmContract]) -> Result<(), Error> {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn append_uuid_list(uuid: &str, hostname: &str) -> Result<(), Error> {
 | 
			
		||||
    use std::{fs::OpenOptions, io::prelude::*};
 | 
			
		||||
    use std::fs::OpenOptions;
 | 
			
		||||
    use std::io::prelude::*;
 | 
			
		||||
    let mut file =
 | 
			
		||||
        OpenOptions::new().create(true).append(true).open(Config::vm_uuid_list_path()?)?;
 | 
			
		||||
    writeln!(file, "{uuid}\t{hostname}")?;
 | 
			
		||||
@ -358,21 +368,104 @@ impl super::HumanOutput for Vec<proto::VmNodeListResp> {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn print_nodes() -> Result<Vec<proto::VmNodeListResp>, Error> {
 | 
			
		||||
pub fn search_nodes(location: Location) -> Result<Vec<proto::VmNodeListResp>, Error> {
 | 
			
		||||
    log::debug!("This will support flags in the future, but we have only one node atm.");
 | 
			
		||||
    let req = proto::VmNodeFilters { ..Default::default() };
 | 
			
		||||
    let req = proto::VmNodeFilters {
 | 
			
		||||
        city: location.city.unwrap_or_default(),
 | 
			
		||||
        country: location.country.unwrap_or_default(),
 | 
			
		||||
        region: location.region.unwrap_or_default(),
 | 
			
		||||
        ..Default::default()
 | 
			
		||||
    };
 | 
			
		||||
    Ok(block_on(grpc::get_node_list(req))?)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Tabled, Debug, Serialize, Deserialize)]
 | 
			
		||||
pub struct NodeOffer {
 | 
			
		||||
    #[tabled(rename = "Location")]
 | 
			
		||||
    pub location: String,
 | 
			
		||||
    #[tabled(rename = "Cores")]
 | 
			
		||||
    pub vcpus: u64,
 | 
			
		||||
    #[tabled(rename = "Mem", display_with = "display_mib_or_gib")]
 | 
			
		||||
    pub mem: u64,
 | 
			
		||||
    #[tabled(rename = "Disk", display_with = "display_mib_or_gib")]
 | 
			
		||||
    pub disk: u64,
 | 
			
		||||
    #[tabled(rename = "Public IPv4", display_with = "display_ip_support")]
 | 
			
		||||
    pub ipv4: bool,
 | 
			
		||||
    #[tabled(rename = "Public IPv6", display_with = "display_ip_support")]
 | 
			
		||||
    pub ipv6: bool,
 | 
			
		||||
    #[tabled(rename = "cost/h")]
 | 
			
		||||
    pub cost_h: f64,
 | 
			
		||||
    #[tabled(rename = "cost/m")]
 | 
			
		||||
    pub cost_m: f64,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn display_ip_support(support: &bool) -> String {
 | 
			
		||||
    match support {
 | 
			
		||||
        true => "Available".to_string(),
 | 
			
		||||
        false => "Unavailable".to_string(),
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl super::HumanOutput for Vec<NodeOffer> {
 | 
			
		||||
    fn human_cli_print(&self) {
 | 
			
		||||
        let style = tabled::settings::Style::rounded();
 | 
			
		||||
        let mut table = tabled::Table::new(self);
 | 
			
		||||
        table.with(style);
 | 
			
		||||
        println!("{table}");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn print_node_offers(location: Location) -> Result<Vec<NodeOffer>, Error> {
 | 
			
		||||
    log::debug!("This will support flags in the future, but we have only one node atm.");
 | 
			
		||||
    let req = proto::VmNodeFilters {
 | 
			
		||||
        city: location.city.unwrap_or_default(),
 | 
			
		||||
        country: location.country.unwrap_or_default(),
 | 
			
		||||
        region: location.region.unwrap_or_default(),
 | 
			
		||||
        ..Default::default()
 | 
			
		||||
    };
 | 
			
		||||
    let node_list = block_on(grpc::get_node_list(req))?;
 | 
			
		||||
    let mut offers: Vec<NodeOffer> = Vec::new();
 | 
			
		||||
    for node in node_list.iter() {
 | 
			
		||||
        let mem_per_cpu = node.memory_mib / node.vcpus;
 | 
			
		||||
        let disk_per_cpu = node.disk_mib / node.vcpus;
 | 
			
		||||
        for i in 1..node.vcpus {
 | 
			
		||||
            let price_per_month = calculate_nanocredits(
 | 
			
		||||
                (node.vcpus * i) as u32,
 | 
			
		||||
                (mem_per_cpu * i) as u32,
 | 
			
		||||
                (disk_per_cpu * i) as u32,
 | 
			
		||||
                false,
 | 
			
		||||
                732,
 | 
			
		||||
                node.price,
 | 
			
		||||
            ) as f64
 | 
			
		||||
                / 1_000_000_000_f64;
 | 
			
		||||
            let price_per_hour = price_per_month / 732_f64;
 | 
			
		||||
            let price_per_month = (price_per_month * 100.0).round() / 100.0;
 | 
			
		||||
            let price_per_hour = (price_per_hour * 1000.0).round() / 1000.0;
 | 
			
		||||
            offers.push(NodeOffer {
 | 
			
		||||
                location: node.city.clone() + ", " + &node.region + ", " + &node.country,
 | 
			
		||||
                vcpus: i,
 | 
			
		||||
                mem: i * mem_per_cpu,
 | 
			
		||||
                disk: i * disk_per_cpu,
 | 
			
		||||
                cost_h: price_per_hour,
 | 
			
		||||
                cost_m: price_per_month,
 | 
			
		||||
                ipv4: node.public_ipv4,
 | 
			
		||||
                ipv6: node.public_ipv6,
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    offers.sort_by_key(|n| n.cost_m as u64);
 | 
			
		||||
    Ok(offers)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn inspect_node(ip: String) -> Result<proto::VmNodeListResp, Error> {
 | 
			
		||||
    let req = proto::VmNodeFilters { ip, ..Default::default() };
 | 
			
		||||
    Ok(block_on(grpc::get_one_node(req))?)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn calculate_nanolp(
 | 
			
		||||
pub fn calculate_nanocredits(
 | 
			
		||||
    vcpus: u32,
 | 
			
		||||
    memory_mb: u32,
 | 
			
		||||
    disk_size_gb: u32,
 | 
			
		||||
    disk_size_mib: u32,
 | 
			
		||||
    public_ipv4: bool,
 | 
			
		||||
    hours: u32,
 | 
			
		||||
    node_price: u64,
 | 
			
		||||
@ -380,19 +473,9 @@ pub fn calculate_nanolp(
 | 
			
		||||
    // this calculation needs to match the calculation of the network
 | 
			
		||||
    let total_units = (vcpus as u64 * 10)
 | 
			
		||||
        + ((memory_mb + 256) as u64 / 200)
 | 
			
		||||
        + (disk_size_gb as u64 / 10)
 | 
			
		||||
        + (disk_size_mib as u64 / 1024 / 10)
 | 
			
		||||
        + (public_ipv4 as u64 * 10);
 | 
			
		||||
    let locked_nano = hours as u64 * 60 * total_units * node_price;
 | 
			
		||||
    eprint!(
 | 
			
		||||
        "Node price: {}/unit/minute. Total Units for hardware requested: {}. ",
 | 
			
		||||
        node_price as f64 / 1_000_000_000.0,
 | 
			
		||||
        total_units,
 | 
			
		||||
    );
 | 
			
		||||
    eprintln!(
 | 
			
		||||
        "Locking {} LP (offering the VM for {} hours).",
 | 
			
		||||
        locked_nano as f64 / 1_000_000_000.0,
 | 
			
		||||
        hours
 | 
			
		||||
    );
 | 
			
		||||
    locked_nano
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
use super::{
 | 
			
		||||
    grpc::{self, proto},
 | 
			
		||||
    injector, Dtrfs, Error,
 | 
			
		||||
};
 | 
			
		||||
// SPDX-License-Identifier: Apache-2.0
 | 
			
		||||
 | 
			
		||||
use super::grpc::{self, proto};
 | 
			
		||||
use super::{injector, Dtrfs, Error};
 | 
			
		||||
use crate::config::Config;
 | 
			
		||||
use crate::utils::block_on;
 | 
			
		||||
use log::{debug, info};
 | 
			
		||||
@ -10,8 +10,8 @@ use log::{debug, info};
 | 
			
		||||
pub struct Request {
 | 
			
		||||
    hostname: String,
 | 
			
		||||
    vcpus: u32,
 | 
			
		||||
    memory_mb: u32,
 | 
			
		||||
    disk_size_gb: u32,
 | 
			
		||||
    memory_mib: u32,
 | 
			
		||||
    disk_size_mib: u32,
 | 
			
		||||
    dtrfs: Option<Dtrfs>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -32,7 +32,8 @@ impl Request {
 | 
			
		||||
                Some(Dtrfs::load_from_file(path)?)
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        let req = Self { hostname, vcpus, memory_mb, disk_size_gb, dtrfs };
 | 
			
		||||
        let req =
 | 
			
		||||
            Self { hostname, vcpus, memory_mib: memory_mb, disk_size_mib: disk_size_gb, dtrfs };
 | 
			
		||||
        if req == Self::default() {
 | 
			
		||||
            log::info!("Skipping hardware upgrade (no arguments specified).");
 | 
			
		||||
            return Ok(());
 | 
			
		||||
@ -53,7 +54,7 @@ impl Request {
 | 
			
		||||
        let updated_contract = block_on(grpc::get_contract_by_uuid(uuid))?;
 | 
			
		||||
        debug!("Got the current contract for the VM after update. {updated_contract:#?}");
 | 
			
		||||
 | 
			
		||||
        if !(self.vcpus != 0 || self.dtrfs.is_some()) {
 | 
			
		||||
        if !(self.vcpus != 0 || self.memory_mib != 0 || self.dtrfs.is_some()) {
 | 
			
		||||
            eprintln!("vCPUs and kernel did not get modified. Secret injection is not required.");
 | 
			
		||||
            return Ok(());
 | 
			
		||||
        }
 | 
			
		||||
@ -68,12 +69,7 @@ impl Request {
 | 
			
		||||
        };
 | 
			
		||||
        let measurement = measurement_args.get_measurement()?;
 | 
			
		||||
 | 
			
		||||
            injector::execute(
 | 
			
		||||
                measurement,
 | 
			
		||||
                args.dtrfs_api_endpoint,
 | 
			
		||||
                None,
 | 
			
		||||
                &updated_contract.hostname,
 | 
			
		||||
            )?;
 | 
			
		||||
        injector::execute(measurement, args.dtrfs_api_endpoint, None, &updated_contract.hostname)?;
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
@ -88,9 +84,9 @@ impl Request {
 | 
			
		||||
            uuid: uuid.to_string(),
 | 
			
		||||
            hostname: self.hostname.clone(),
 | 
			
		||||
            admin_pubkey: Config::get_detee_wallet()?,
 | 
			
		||||
            disk_size_gb: self.disk_size_gb,
 | 
			
		||||
            disk_size_mib: self.disk_size_mib * 1024,
 | 
			
		||||
            vcpus: self.vcpus,
 | 
			
		||||
            memory_mb: self.memory_mb,
 | 
			
		||||
            memory_mib: self.memory_mib * 1024,
 | 
			
		||||
            kernel_url,
 | 
			
		||||
            kernel_sha,
 | 
			
		||||
            dtrfs_url,
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										72
									
								
								src/utils.rs
									
									
									
									
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										72
									
								
								src/utils.rs
									
									
									
									
									
								
							@ -1,8 +1,9 @@
 | 
			
		||||
// SPDX-License-Identifier: Apache-2.0
 | 
			
		||||
 | 
			
		||||
use crate::config::Config;
 | 
			
		||||
use tonic::{
 | 
			
		||||
    metadata::{errors::InvalidMetadataValue, AsciiMetadataValue},
 | 
			
		||||
    Request,
 | 
			
		||||
};
 | 
			
		||||
use tonic::metadata::errors::InvalidMetadataValue;
 | 
			
		||||
use tonic::metadata::AsciiMetadataValue;
 | 
			
		||||
use tonic::Request;
 | 
			
		||||
 | 
			
		||||
#[derive(thiserror::Error, Debug)]
 | 
			
		||||
pub enum Error {
 | 
			
		||||
@ -42,3 +43,66 @@ pub fn shorten_string(my_string: &String) -> String {
 | 
			
		||||
        format!("{}", first_part)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn display_mib_or_gib(value: &u64) -> String {
 | 
			
		||||
    if *value >= 1024 {
 | 
			
		||||
        if *value < 102400 {
 | 
			
		||||
            let value = (value / 102) as f64;
 | 
			
		||||
            format!("{}G", value / 10_f64)
 | 
			
		||||
        } else {
 | 
			
		||||
            format!("{}G", value / 1024)
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        format!("{}M", value)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[macro_export]
 | 
			
		||||
macro_rules! call_with_follow_redirect {
 | 
			
		||||
    (
 | 
			
		||||
        $client:expr,
 | 
			
		||||
        $req_data:expr,
 | 
			
		||||
        $method:ident
 | 
			
		||||
    ) => {
 | 
			
		||||
        async {
 | 
			
		||||
            let mut client = $client;
 | 
			
		||||
 | 
			
		||||
            for attempt in 0..crate::constants::MAX_REDIRECTS {
 | 
			
		||||
                log::debug!(
 | 
			
		||||
                    "Attempt #{}: Calling method '{}'...",
 | 
			
		||||
                    attempt + 1,
 | 
			
		||||
                    stringify!($method)
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                let req_data_clone = $req_data.clone();
 | 
			
		||||
                let signed_req = crate::utils::sign_request(req_data_clone)?;
 | 
			
		||||
 | 
			
		||||
                match client.$method(signed_req).await {
 | 
			
		||||
                    Ok(resp) => return Ok(resp),
 | 
			
		||||
 | 
			
		||||
                    Err(status)
 | 
			
		||||
                        if status.code() == tonic::Code::Unavailable
 | 
			
		||||
                            && status.message() == "moved" =>
 | 
			
		||||
                    {
 | 
			
		||||
                        let redirect_url = status
 | 
			
		||||
                            .metadata()
 | 
			
		||||
                            .get("location")
 | 
			
		||||
                            .and_then(|v| v.to_str().ok())
 | 
			
		||||
                            .ok_or_else(|| {
 | 
			
		||||
                                Error::RedirectError(
 | 
			
		||||
                                    "Server indicated a move but provided no location".into(),
 | 
			
		||||
                                )
 | 
			
		||||
                            })?;
 | 
			
		||||
 | 
			
		||||
                        log::info!("Server moved. Redirecting to {}...", redirect_url);
 | 
			
		||||
 | 
			
		||||
                        client = client_from_endpoint(format!("https://{}", redirect_url)).await?;
 | 
			
		||||
                        continue;
 | 
			
		||||
                    }
 | 
			
		||||
                    Err(e) => return Err(Error::ResponseStatus(e)),
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Err(Error::MaxRedirectsExceeded(crate::constants::MAX_REDIRECTS.to_string()))
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user