mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-31 04:23:40 +00:00
Compare commits
185 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
52c389e669 | ||
|
|
4d3d5fc94a | ||
|
|
5a4e980d31 | ||
|
|
8c79984e80 | ||
|
|
53db8fc4ec | ||
|
|
d2a70a782b | ||
|
|
8096bef541 | ||
|
|
b8d84c5fc2 | ||
|
|
d3dbdebbcf | ||
|
|
55179e3ead | ||
|
|
bcbd972502 | ||
|
|
fe7410c0fa | ||
|
|
4fba3a1d75 | ||
|
|
c61c164c0f | ||
|
|
ba04b7c431 | ||
|
|
39accaa382 | ||
|
|
219f66ae8a | ||
|
|
e482ccf7fd | ||
|
|
e96ef695a3 | ||
|
|
833941b031 | ||
|
|
7417bfdbfa | ||
|
|
df05fade25 | ||
|
|
47e5951fc3 | ||
|
|
c1b6d5e1e4 | ||
|
|
832d7aaa6c | ||
|
|
db8a26c84c | ||
|
|
18f18e3b95 | ||
|
|
7ed220dc51 | ||
|
|
d224b7a114 | ||
|
|
66ddf752ac | ||
|
|
b72252b437 | ||
|
|
e74ab3ce26 | ||
|
|
b7821576bb | ||
|
|
b717853759 | ||
|
|
ebd4cb8480 | ||
|
|
1188d67e30 | ||
|
|
25c55ea426 | ||
|
|
f993f19614 | ||
|
|
e38baa4779 | ||
|
|
b3ab312088 | ||
|
|
41b01efed3 | ||
|
|
d211de9782 | ||
|
|
408cc45688 | ||
|
|
68a87c8c4f | ||
|
|
718d556080 | ||
|
|
71f2c88b8f | ||
|
|
49628f07e6 | ||
|
|
9f45879c7f | ||
|
|
fcb807eb42 | ||
|
|
d5d0ea3ade | ||
|
|
50b887fcb9 | ||
|
|
fc9b3a6d69 | ||
|
|
0c7eae7333 | ||
|
|
792a5cc429 | ||
|
|
8e46719b49 | ||
|
|
aef63a60c2 | ||
|
|
567ac9fa50 | ||
|
|
eb5473087b | ||
|
|
5d04f1f6b0 | ||
|
|
8bdb2e6f3b | ||
|
|
a122c4a058 | ||
|
|
b908967ac4 | ||
|
|
ad92660c76 | ||
|
|
070835c40e | ||
|
|
16a4c41886 | ||
|
|
da922f498e | ||
|
|
c315dbaadf | ||
|
|
939ad844e8 | ||
|
|
778d22ab2b | ||
|
|
29d5e3b36e | ||
|
|
c4eef4db17 | ||
|
|
73d40c71ad | ||
|
|
2022a7fc1d | ||
|
|
fb9b7d2a58 | ||
|
|
c72b7425fc | ||
|
|
9066d77a70 | ||
|
|
27f05a4588 | ||
|
|
ce8280b0ba | ||
|
|
7c3238cf8d | ||
|
|
81d11842f0 | ||
|
|
238ede33b9 | ||
|
|
071bd159ab | ||
|
|
22da61b05a | ||
|
|
ee13552c21 | ||
|
|
d7508345eb | ||
|
|
90b412384a | ||
|
|
ebd74cca3c | ||
|
|
5fbf34a84e | ||
|
|
271dd3e12d | ||
|
|
4f315e9958 | ||
|
|
57955ef22e | ||
|
|
3ecfb4e4eb | ||
|
|
90227c0606 | ||
|
|
234fc06f76 | ||
|
|
b9a3a0cb8d | ||
|
|
e2a1ac7033 | ||
|
|
23077c6c6b | ||
|
|
c25295500b | ||
|
|
4da4fd66a3 | ||
|
|
1b1fd40e08 | ||
|
|
b80634df83 | ||
|
|
b4d1db7e11 | ||
|
|
2d10220e52 | ||
|
|
34d52d063e | ||
|
|
8b21ed13ce | ||
|
|
4aaddb233a | ||
|
|
d6dfdda061 | ||
|
|
da605e419b | ||
|
|
54e1acc6b6 | ||
|
|
a896f4c7a1 | ||
|
|
0cd2a32b24 | ||
|
|
31318687bf | ||
|
|
f53581be8c | ||
|
|
450ce68c8a | ||
|
|
06b34d588e | ||
|
|
66c08c730b | ||
|
|
5b248013e5 | ||
|
|
865bb12f31 | ||
|
|
9f9d32e0db | ||
|
|
0874d77403 | ||
|
|
0962a20852 | ||
|
|
3a63dab586 | ||
|
|
4e0ad21384 | ||
|
|
89ff5de01b | ||
|
|
6da3c7e326 | ||
|
|
7acfd2da5b | ||
|
|
becb8c5c35 | ||
|
|
0035a2062e | ||
|
|
e8a693049e | ||
|
|
69cfe3daf4 | ||
|
|
83dbc0f0cc | ||
|
|
e5a844e0d1 | ||
|
|
626943f98c | ||
|
|
3a4270bc85 | ||
|
|
2f6bd4b1b6 | ||
|
|
1cce947846 | ||
|
|
c4703ef50b | ||
|
|
c98f7ebd34 | ||
|
|
308a78de37 | ||
|
|
d8ea87bbae | ||
|
|
db275601d4 | ||
|
|
039f776ead | ||
|
|
14127daa41 | ||
|
|
da6ee94605 | ||
|
|
abec910378 | ||
|
|
ba452587fc | ||
|
|
4de0c97bb5 | ||
|
|
5b3c692b7a | ||
|
|
2c97294196 | ||
|
|
0f43e5282f | ||
|
|
7ce276c267 | ||
|
|
7a3811235e | ||
|
|
7b0f99881b | ||
|
|
431ff86647 | ||
|
|
47e3361c4f | ||
|
|
968b94e81f | ||
|
|
c4fe73398e | ||
|
|
95a0bfdd1e | ||
|
|
37c772ff8c | ||
|
|
c6347c3ff7 | ||
|
|
adea594e28 | ||
|
|
a9c51e24f1 | ||
|
|
c3e96ef5df | ||
|
|
bd046f7e9f | ||
|
|
ab6aadc3b4 | ||
|
|
2b4c456c5d | ||
|
|
e4cbc38bfd | ||
|
|
bcfe7c0d21 | ||
|
|
7d493e12d3 | ||
|
|
414d8ae54a | ||
|
|
1da2da7e43 | ||
|
|
1a66a5d240 | ||
|
|
7939dcc4e2 | ||
|
|
48f6543cb9 | ||
|
|
3fce90b7a8 | ||
|
|
71a3728fbb | ||
|
|
0dee648078 | ||
|
|
26cf4ea418 | ||
|
|
f6ac172ef3 | ||
|
|
3ababacd91 | ||
|
|
5a121945d2 | ||
|
|
30f8b8e6cd | ||
|
|
f68c13f57f | ||
|
|
3a082c108b | ||
|
|
3efb38a742 |
237
CONTRIBUTING.md
Normal file
237
CONTRIBUTING.md
Normal file
@@ -0,0 +1,237 @@
|
||||
<!-- omit in toc -->
|
||||
# Contributing to Embassy OS
|
||||
|
||||
First off, thanks for taking the time to contribute! ❤️
|
||||
|
||||
All types of contributions are encouraged and valued. See the [Table of Contents](#table-of-contents) for different ways to help and details about how this project handles them. Please make sure to read the relevant section before making your contribution. It will make it a lot easier for us maintainers and smooth out the experience for all involved. The community looks forward to your contributions. 🎉
|
||||
|
||||
> And if you like the project, but just don't have time to contribute, that's fine. There are other easy ways to support the project and show your appreciation, which we would also be very happy about:
|
||||
> - Star the project
|
||||
> - Tweet about it
|
||||
> - Refer this project in your project's readme
|
||||
> - Mention the project at local meetups and tell your friends/colleagues
|
||||
> - Buy an [Embassy](https://start9labs.com)
|
||||
|
||||
<!-- omit in toc -->
|
||||
## Table of Contents
|
||||
|
||||
- [I Have a Question](#i-have-a-question)
|
||||
- [I Want To Contribute](#i-want-to-contribute)
|
||||
- [Reporting Bugs](#reporting-bugs)
|
||||
- [Suggesting Enhancements](#suggesting-enhancements)
|
||||
- [Your First Code Contribution](#your-first-code-contribution)
|
||||
- [Setting Up Your Development Environment](#setting-up-your-development-environment)
|
||||
- [Building The Image](#building-the-image)
|
||||
- [Improving The Documentation](#improving-the-documentation)
|
||||
- [Styleguides](#styleguides)
|
||||
- [Formatting](#formatting)
|
||||
- [Atomic Commits](#atomic-commits)
|
||||
- [Commit Messages](#commit-messages)
|
||||
- [Pull Requests](#pull-requests)
|
||||
- [Rebasing Changes](#rebasing-changes)
|
||||
- [Join The Discussion](#join-the-discussion)
|
||||
- [Join The Project Team](#join-the-project-team)
|
||||
|
||||
|
||||
|
||||
## I Have a Question
|
||||
|
||||
> If you want to ask a question, we assume that you have read the available [Documentation](https://docs.start9labs.com).
|
||||
|
||||
Before you ask a question, it is best to search for existing [Issues](https://github.com/Start9Labs/embassy-os/issues) that might help you. In case you have found a suitable issue and still need clarification, you can write your question in this issue. It is also advisable to search the internet for answers first.
|
||||
|
||||
If you then still feel the need to ask a question and need clarification, we recommend the following:
|
||||
|
||||
- Open an [Issue](https://github.com/Start9Labs/embassy-os/issues/new).
|
||||
- Provide as much context as you can about what you're running into.
|
||||
- Provide project and platform versions, depending on what seems relevant.
|
||||
|
||||
We will then take care of the issue as soon as possible.
|
||||
|
||||
<!--
|
||||
You might want to create a separate issue tag for questions and include it in this description. People should then tag their issues accordingly.
|
||||
|
||||
Depending on how large the project is, you may want to outsource the questioning, e.g. to Stack Overflow or Gitter. You may add additional contact and information possibilities:
|
||||
- IRC
|
||||
- Slack
|
||||
- Gitter
|
||||
- Stack Overflow tag
|
||||
- Blog
|
||||
- FAQ
|
||||
- Roadmap
|
||||
- E-Mail List
|
||||
- Forum
|
||||
-->
|
||||
|
||||
## I Want To Contribute
|
||||
|
||||
> ### Legal Notice <!-- omit in toc -->
|
||||
> When contributing to this project, you must agree that you have authored 100% of the content, that you have the necessary rights to the content and that the content you contribute may be provided under the project license.
|
||||
|
||||
### Reporting Bugs
|
||||
|
||||
<!-- omit in toc -->
|
||||
#### Before Submitting a Bug Report
|
||||
|
||||
A good bug report shouldn't leave others needing to chase you up for more information. Therefore, we ask you to investigate carefully, collect information and describe the issue in detail in your report. Please complete the following steps in advance to help us fix any potential bug as fast as possible.
|
||||
|
||||
- Make sure that you are using the latest version.
|
||||
- Determine if your bug is really a bug and not an error on your side e.g. using incompatible environment components/versions (Make sure that you have read the [documentation](https://docs.start9labs.com). If you are looking for support, you might want to check [this section](#i-have-a-question)).
|
||||
- To see if other users have experienced (and potentially already solved) the same issue you are having, check if there is not already a bug report existing for your bug or error in the [bug tracker](https://github.com/Start9Labs/embassy-osissues?q=label%3Abug).
|
||||
- Also make sure to search the internet (including Stack Overflow) to see if users outside of the GitHub community have discussed the issue.
|
||||
- Collect information about the bug:
|
||||
- Stack trace (Traceback)
|
||||
- Client OS, Platform and Version (Windows/Linux/macOS/iOS/Android, Firefox/Tor Browser/Consulate)
|
||||
- Version of the interpreter, compiler, SDK, runtime environment, package manager, depending on what seems relevant.
|
||||
- Possibly your input and the output
|
||||
- Can you reliably reproduce the issue? And can you also reproduce it with older versions?
|
||||
|
||||
<!-- omit in toc -->
|
||||
#### How Do I Submit a Good Bug Report?
|
||||
|
||||
> You must never report security related issues, vulnerabilities or bugs to the issue tracker, or elsewhere in public. Instead sensitive bugs must be sent by email to <security@start9labs.com>.
|
||||
<!-- You may add a PGP key to allow the messages to be sent encrypted as well. -->
|
||||
|
||||
We use GitHub issues to track bugs and errors. If you run into an issue with the project:
|
||||
|
||||
- Open an [Issue](https://github.com/Start9Labs/embassy-os/issues/new). (Since we can't be sure at this point whether it is a bug or not, we ask you not to talk about a bug yet and not to label the issue.)
|
||||
- Explain the behavior you would expect and the actual behavior.
|
||||
- Please provide as much context as possible and describe the *reproduction steps* that someone else can follow to recreate the issue on their own. This usually includes your code. For good bug reports you should isolate the problem and create a reduced test case.
|
||||
- Provide the information you collected in the previous section.
|
||||
|
||||
Once it's filed:
|
||||
|
||||
- The project team will label the issue accordingly.
|
||||
- A team member will try to reproduce the issue with your provided steps. If there are no reproduction steps or no obvious way to reproduce the issue, the team will ask you for those steps and mark the issue as `needs-repro`. Bugs with the `needs-repro` tag will not be addressed until they are reproduced.
|
||||
- If the team is able to reproduce the issue, it will be marked `needs-fix`, as well as possibly other tags (such as `critical`), and the issue will be left to be [implemented by someone](#your-first-code-contribution).
|
||||
|
||||
<!-- You might want to create an issue template for bugs and errors that can be used as a guide and that defines the structure of the information to be included. If you do so, reference it here in the description. -->
|
||||
|
||||
|
||||
### Suggesting Enhancements
|
||||
|
||||
This section guides you through submitting an enhancement suggestion for Embassy OS, **including completely new features and minor improvements to existing functionality**. Following these guidelines will help maintainers and the community to understand your suggestion and find related suggestions.
|
||||
|
||||
<!-- omit in toc -->
|
||||
#### Before Submitting an Enhancement
|
||||
|
||||
- Make sure that you are using the latest version.
|
||||
- Read the [documentation](https://docs.start9labs.com) carefully and find out if the functionality is already covered, maybe by an individual configuration.
|
||||
- Perform a [search](https://github.com/Start9Labs/embassy-os/issues) to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one.
|
||||
- Find out whether your idea fits with the scope and aims of the project. It's up to you to make a strong case to convince the project's developers of the merits of this feature. Keep in mind that we want features that will be useful to the majority of our users and not just a small subset. If you're just targeting a minority of users, consider writing an add-on/plugin library.
|
||||
|
||||
<!-- omit in toc -->
|
||||
#### How Do I Submit a Good Enhancement Suggestion?
|
||||
|
||||
Enhancement suggestions are tracked as [GitHub issues](https://github.com/Start9Labs/embassy-os/issues).
|
||||
|
||||
- Use a **clear and descriptive title** for the issue to identify the suggestion.
|
||||
- Provide a **step-by-step description of the suggested enhancement** in as many details as possible.
|
||||
- **Describe the current behavior** and **explain which behavior you expected to see instead** and why. At this point you can also tell which alternatives do not work for you.
|
||||
- You may want to **include screenshots and animated GIFs** which help you demonstrate the steps or point out the part which the suggestion is related to. You can use [this tool](https://www.cockos.com/licecap/) to record GIFs on macOS and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or [this tool](https://github.com/GNOME/byzanz) on Linux. <!-- this should only be included if the project has a GUI -->
|
||||
- **Explain why this enhancement would be useful** to most Embassy OS users. You may also want to point out the other projects that solved it better and which could serve as inspiration.
|
||||
|
||||
<!-- You might want to create an issue template for enhancement suggestions that can be used as a guide and that defines the structure of the information to be included. If you do so, reference it here in the description. -->
|
||||
|
||||
### Project Structure
|
||||
Embassy OS has 3 main components: `agent`, `appmgr`, and `ui`.
|
||||
- The `ui` (Typescript Ionic Angular) is the code that is deployed to the browser to provide the user interface for Embassy OS
|
||||
- The `agent` (Haskell) is a daemon that provides the interface for the `ui` to interact with the Embassy, as well as manage system state.
|
||||
- `appmgr` (Rust) is a command line utility and (soon to be) daemon that sets up and manages services and their environments.
|
||||
|
||||
### Your First Code Contribution
|
||||
|
||||
#### Setting up your development environment
|
||||
##### agent
|
||||
There are two main workflows to consider when developing on the agent. During the development process you will spend
|
||||
most of your time developing in an environment where you cannot actually run the agent. This is because we make heavy
|
||||
platform specific assumptions (by nature of the project) around what folders get used and what package management tools
|
||||
are used for the underlying system. If you are running this on a platform besides Linux you won't even be able to run
|
||||
the agent effectively on your dev machine. Even if you are on Linux you may not want to turn administrative control over
|
||||
to the software you are currently developing. So how do you know that anything you are doing is right? We make extensive
|
||||
use of Haskell's type system and surrounding tooling. For this you will want to make sure you are using the [haskell-language-server](https://github.com/haskell/haskell-language-server)
|
||||
and [stack](https://github.com/commercialhaskell/stack)
|
||||
|
||||
At some point though you will want to build the agent for the target platform (Raspberry Pi 4). This is the second build
|
||||
flow that you will need to consider.
|
||||
|
||||
At Start9 we build the agent in two different ways. The primary way we have done it is on the Raspberry Pi itself. To do
|
||||
this you will need stack built for the Raspberry Pi. Unfortunately, however, FPComplete no longer
|
||||
distributes ARMv7 binaries for stack. Though hopefully soon we will be able to submit the binaries we've built for this
|
||||
project back to them and have them hosted more visibly. The way we bootstrap through this problem is by downloading version
|
||||
[2.1.3](https://github.com/commercialhaskell/stack/releases/download/v2.1.3/stack-2.1.3-linux-arm.tar.gz) and using that
|
||||
to compile v2.5.1. Before you can successfully compile anything with GHC on the Raspberry Pi. You will need to tweak the
|
||||
relevant GHC config. You will need to edit the file at `~/.stack/programs/arm-linux/ghc-8.10.2/lib/ghc-8.10.2/settings`
|
||||
and change the line `("C compiler flags", " -marm -fno-stack-protector -mcpu=cortex-a7")` to include `-mcpu=cortex-a7`.
|
||||
You will also need to make sure you've downloaded and installed LLVM 9.
|
||||
|
||||
Once you have done these things, you simply need to `cd` into the embassy-os project and then run `make agent`.
|
||||
|
||||
##### ui
|
||||
- Requirements
|
||||
- [Install nodejs](https://nodejs.org/en/)
|
||||
- [Install npm](https://www.npmjs.com/get-npm)
|
||||
- [Install ionic cli](https://ionicframework.com/docs/intro/cli)
|
||||
- Scripts (run within ./ui directory)
|
||||
- `npm i` installs ui node package dependencies
|
||||
- `npm run build` compiles project, depositing build artifacts into ./ui/www
|
||||
- `npm run build-prod` as above but customized for deployment to an Embassy
|
||||
- `ionic serve` serves the ui on localhost:8100 for local development. Edit ./ui/use-mocks.json to 'true' to use mocks during local development
|
||||
- `./build-send.sh <embassy .local address suffix>` builds the project and deploys it to the referenced Embassy
|
||||
- Find your Embassy on the LAN using the Start9 Setup App or network tools. It's address will be of the form `start9-<suffix>.local`.
|
||||
- For example `./build-send.sh abcdefgh` will deploy the ui to the Embassy with LAN address `start9-abcdefgh.local`.
|
||||
- SSH keys must be installed on the Embassy prior to running this script.
|
||||
|
||||
##### appmgr
|
||||
- [Install Rust](https://rustup.rs)
|
||||
- Recommended: [rust-analyzer](https://rust-analyzer.github.io/)
|
||||
|
||||
#### Building The Image
|
||||
- Requirements
|
||||
- `ext4fs` (available if running on the Linux kernel)
|
||||
- [Docker](https://docs.docker.com/get-docker/)
|
||||
- GNU Make
|
||||
- Building
|
||||
- build the [agent](#agent)
|
||||
- make sure resulting artifact is agent/dist/agent
|
||||
- run `make`
|
||||
|
||||
### Improving The Documentation
|
||||
You can find the repository for Start9's documentation [here](https://github.com/Start9Labs/documentation). If there is something you would like to see added, let us know, or create an issue yourself. Welcome are contributions for lacking or incorrect information, broken links, requested additions, or general style improvements.
|
||||
|
||||
Contributions in the form of setup guides for integrations with external applications are highly encouraged. If you struggled through a process and would like to share your steps with others, check out the docs for each [service](https://github.com/Start9Labs/documentation/blob/master/source/user-manuals/available-services/index.rst) we support. The wrapper repos contain sections for adding integration guides, such as this [one](https://github.com/Start9Labs/bitcoind-wrapper/tree/master/docs). These not only help out others in the community, but inform how we can create a more seamless and intuitive experience.
|
||||
|
||||
## Styleguides
|
||||
### Formatting
|
||||
Code must be formatted with the formatter designated for each component:
|
||||
- `ui`: [tslint](https://palantir.github.io/tslint/)
|
||||
- `agent`: [brittany](https://github.com/lspitzner/brittany)
|
||||
- `appmgr`: [rustfmt](https://github.com/rust-lang/rustfmt)
|
||||
|
||||
### Atomic Commits
|
||||
Commits [should be atomic](https://en.wikipedia.org/wiki/Atomic_commit#Atomic_commit_convention) and diffs should be easy to read.
|
||||
Do not mix any formatting fixes or code moves with actual code changes.
|
||||
|
||||
### Commit Messages
|
||||
If a commit touches only 1 component, prefix the message with the affected component. i.e. `appmgr: update to tokio v0.3`.
|
||||
|
||||
### Pull Requests
|
||||
The body of a pull request should contain sufficient description of what the changes do, as well as a justification.
|
||||
You should include references to any relevant [issues](https://github.com/Start9Labs/embassy-os/issues).
|
||||
|
||||
### Rebasing Changes
|
||||
When a pull request conflicts with the target branch, you may be asked to rebase it on top of the current target branch. The git rebase command will take care of rebuilding your commits on top of the new base.
|
||||
|
||||
This project aims to have a clean git history, where code changes are only made in non-merge commits. This simplifies auditability because merge commits can be assumed to not contain arbitrary code changes.
|
||||
|
||||
## Join The Discussion
|
||||
Current or aspiring contributors? Join our community developer Matrix channel: `#community-dev:matrix.start9labs.com`.
|
||||
|
||||
Just interested in or using the project? Join our community [Telegram](https://t.me/start9_labs) or Matrix channel: `#community:matrix.start9labs.com`.
|
||||
|
||||
## Join The Project Team
|
||||
Interested in becoming a part of the Start9 Labs team? Send an email to <jobs@start9labs.com>
|
||||
|
||||
<!-- omit in toc -->
|
||||
## Attribution
|
||||
This guide is based on the **contributing-gen**. [Make your own](https://github.com/bttger/contributing-gen)!
|
||||
33
LICENSE.md
33
LICENSE.md
@@ -2,17 +2,24 @@
|
||||
|
||||
This license governs the use of the accompanying Software. If you use the Software, you accept this license. If you do not accept the license, do not use the Software.
|
||||
|
||||
1. **Definitions**
|
||||
1. "Licensor" means the copyright owner, Start9 Labs, Inc, or its successor(s) in interest, or a future assignee of the copyright.
|
||||
2. "Source Code" means the code made available to you by the Licensor pursuant to the terms of this license and any derivative works based thereon.
|
||||
3. "Object Code" means any non-source form of the Source Code, including the machine-language output by a compiler or assembler.
|
||||
4. "Distribute" means to convey or to publish and generally has the same meaning here as under U.S. Copyright law.
|
||||
5. "Personal Use" means accessing, copying, reviewing, auditing, running, testing, or modifying the Source Code.
|
||||
2. **Grant of Rights**
|
||||
1. Subject to the terms of this license, the Licensor grants you, the licensee, a non-exclusive, worldwide, royalty-free copyright license to the Source Code for Personal Use only.
|
||||
2. Subject to the terms of this license, the Licensor grants you, the licensee, the right to Distribute the Source Code or modifications to the Source Code.
|
||||
3. Distributing the Object Code, or any Object Code created based on your modifications to the Source Code, is not permitted under the terms of this license without express written consent of the Licensor.
|
||||
4. If you Distribute the Source Code, or if permission is granted to Distribute the Object Code, you expressly undertake not to remove, or modify, in any manner, the copyright notices attached to the Source Code, and displayed in any output of the Object Code when run, and to reproduce these notices, in an identical manner, in any distributed copies of the Source Code or Object Code together with a copy of this license. If you Distribute a modified copy of the Software or a derivative work based thereon, the work must carry prominent notices stating that you modified it, and giving a relevant date.
|
||||
5. The terms of this license will apply to anyone who comes into possession of a copy of the Source Code or Object Code, and any modifications or derivative works based thereon, made by anyone.
|
||||
3. **Disclaimer.** THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Licensor has no obligation to support recipients of the Source Code or Object Code.
|
||||
1. **Definitions.**
|
||||
1. “Licensor” means the copyright owner, Start9 Labs, Inc, or its successor(s) in interest, or a future assignee of the copyright.
|
||||
2. “Source Code” means the preferred form of the Software for making modifications to it.
|
||||
3. “Object Code” means any non-source form of the Software, including the machine-language output by a compiler or assembler.
|
||||
4. “Distribute” means to convey or to publish and generally has the same meaning here as under U.S. Copyright law.
|
||||
5. “Sell” means practicing any or all of the rights granted to you under the License to provide to third parties, for a fee or other consideration (including without limitation fees for hosting or consulting/support services related to the Software), a product or service whose value derives, entirely or substantially, from the functionality of the Software.
|
||||
|
||||
2. **Grant of Rights.** Subject to the terms of this license, the Licensor grants you, the licensee, a non-exclusive, worldwide, royalty-free copyright license to:
|
||||
1. Access, audit, copy, modify, compile, or distribute the Source Code or modifications to the Source Code.
|
||||
2. Run, test, or otherwise use the Object Code.
|
||||
|
||||
3. **Limitations.**
|
||||
1. The grant of rights under the License will NOT include, and the License does NOT grant you the right to:
|
||||
1. Sell the Software or any derivative works based thereon.
|
||||
2. Distribute the Object Code.
|
||||
2. If you Distribute the Source Code, or if permission is separately granted to Distribute the Object Code, you expressly undertake not to remove, or modify, in any manner, the copyright notices attached to the Source Code, and displayed in any output of the Object Code when run, and to reproduce these notices, in an identical manner, in any distributed copies of the Software together with a copy of this license. If you Distribute a modified copy of the Software, or a derivative work based thereon, the work must carry prominent notices stating that you modified it, and giving a relevant date.
|
||||
3. The terms of this license will apply to anyone who comes into possession of a copy of the Software, and any modifications or derivative works based thereon, made by anyone.
|
||||
|
||||
4. **Contributions.** You hereby grant to Licensor a perpetual, irrevocable, worldwide, non-exclusive, royalty-free license to use and exploit any modifications or derivative works based on the Source Code of which you are the author.
|
||||
|
||||
5. **Disclaimer.** THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. LICENSOR HAS NO OBLIGATION TO SUPPORT RECIPIENTS OF THE SOFTWARE.
|
||||
|
||||
45
Makefile
45
Makefile
@@ -1,12 +1,20 @@
|
||||
APPMGR_SRC := $(shell find appmgr/src) appmgr/Cargo.toml appmgr/Cargo.lock
|
||||
LIFELINE_SRC := $(shell find lifeline/src) lifeline/Cargo.toml lifeline/Cargo.lock
|
||||
AGENT_SRC := $(shell find agent/src) $(shell find agent/config) agent/stack.yaml agent/package.yaml agent/build.sh
|
||||
|
||||
.DELETE_ON_ERROR:
|
||||
UI_SRC := $(shell find ui/src) \
|
||||
ui/angular.json \
|
||||
ui/browserslist \
|
||||
ui/client-manifest.yaml \
|
||||
ui/ionic.config.json \
|
||||
ui/postprocess.ts \
|
||||
ui/tsconfig.json \
|
||||
ui/tslint.json \
|
||||
ui/use-mocks.json
|
||||
|
||||
all: embassy.img
|
||||
|
||||
embassy.img: buster.img product_key appmgr/target/armv7-unknown-linux-musleabihf/release/appmgr ui/www agent/dist/agent agent/config/agent.service
|
||||
./make_image.sh
|
||||
embassy.img: buster.img product_key appmgr/target/armv7-unknown-linux-gnueabihf/release/appmgr ui/www agent/dist/agent agent/config/agent.service lifeline/target/armv7-unknown-linux-gnueabihf/release/lifeline lifeline/lifeline.service setup.sh setup.service docker-daemon.json
|
||||
sudo ./make_image.sh
|
||||
|
||||
buster.img:
|
||||
wget -O buster.zip https://downloads.raspberrypi.org/raspios_lite_armhf/images/raspios_lite_armhf-2020-08-24/2020-08-20-raspios-buster-armhf-lite.zip
|
||||
@@ -18,9 +26,28 @@ product_key:
|
||||
echo "X\c" > product_key
|
||||
cat /dev/random | base32 | head -c11 | tr '[:upper:]' '[:lower:]' >> product_key
|
||||
|
||||
appmgr/target/armv7-unknown-linux-musleabihf/release/appmgr: $(APPMGR_SRC)
|
||||
docker run --rm -it -v ~/.cargo/registry:/root/.cargo/registry -v "$(shell pwd)"/appmgr:/home/rust/src start9/rust-arm-cross:latest cargo build --release --features=production
|
||||
docker run --rm -it -v ~/.cargo/registry:/root/.cargo/registry -v "$(shell pwd)"/appmgr:/home/rust/src start9/rust-arm-cross:latest arm-linux-gnueabi-strip target/armv7-unknown-linux-gnueabihf/release/appmgr
|
||||
appmgr/target/armv7-unknown-linux-gnueabihf/release/appmgr: $(APPMGR_SRC)
|
||||
docker run --rm -it -v ~/.cargo/registry:/root/.cargo/registry -v "$(shell pwd)":/home/rust/src start9/rust-arm-cross:latest sh -c "(cd appmgr && cargo build --release --features=production)"
|
||||
docker run --rm -it -v ~/.cargo/registry:/root/.cargo/registry -v "$(shell pwd)":/home/rust/src start9/rust-arm-cross:latest arm-linux-gnueabi-strip appmgr/target/armv7-unknown-linux-gnueabihf/release/appmgr
|
||||
|
||||
appmgr: appmgr/target/armv7-unknown-linux-gnueabihf/release/appmgr
|
||||
|
||||
agent/dist/agent: $(AGENT_SRC)
|
||||
(cd agent && ./build.sh)
|
||||
|
||||
agent: agent/dist/agent
|
||||
|
||||
ui/node_modules: ui/package.json
|
||||
npm --prefix ui install
|
||||
|
||||
ui/www: $(UI_SRC) ui/node_modules
|
||||
npm --prefix ui run build-prod
|
||||
|
||||
ui: ui/www
|
||||
|
||||
lifeline/target/armv7-unknown-linux-gnueabihf/release/lifeline: $(LIFELINE_SRC)
|
||||
docker run --rm -it -v ~/.cargo/registry:/root/.cargo/registry -v "$(shell pwd)":/home/rust/src start9/rust-arm-cross:latest sh -c "(cd lifeline && cargo build --release)"
|
||||
docker run --rm -it -v ~/.cargo/registry:/root/.cargo/registry -v "$(shell pwd)":/home/rust/src start9/rust-arm-cross:latest arm-linux-gnueabi-strip lifeline/target/armv7-unknown-linux-gnueabihf/release/lifeline
|
||||
|
||||
lifeline: lifeline/target/armv7-unknown-linux-gnueabihf/release/lifeline
|
||||
|
||||
agent: $(AGENT_SRC)
|
||||
(cd agent; ./build.sh)
|
||||
|
||||
36
README.md
36
README.md
@@ -1 +1,35 @@
|
||||
# Embassy OS
|
||||
# EmbassyOS
|
||||
[](https://github.com/Start9Labs/embassy-os/releases)
|
||||
[](https://t.me/start9_labs)
|
||||
[](https://docs.start9labs.com)
|
||||
[](https://matrix.to/#/#community-dev:matrix.start9labs.com)
|
||||
[](https://start9labs.com)
|
||||
|
||||
[](https://twitter.com/start9labs)
|
||||
|
||||
### _Anyone can do it. No one can stop it._ ###
|
||||
|
||||
EmbassyOS is a mass-market, graphical operating system designed to facilitate the discovery, installation, configuration, private self-hosting, and reliable operation of open-source software services and applications. It aims to eliminate trust and custodianship from personal computing.
|
||||
|
||||

|
||||
|
||||
## ⚠️ Caution
|
||||
Some technologies supported by this software, such as [Lightning](https://lightning.network/), are considered in active development and might experience issues. Do not commit any funds you are not willing to loose. Be #reckless at your own risk.
|
||||
|
||||
## Running EmbassyOS
|
||||
There are multiple ways to obtain and begin using EmbassyOS.
|
||||
|
||||
### Buy an Embassy
|
||||
This is the most convenient option. Simply [buy an Embassy](https://start9labs.com) from Start9 Labs and plug it in. Depending on where you live, shipping costs and import duties may vary.
|
||||
|
||||
### Build your own Embassy
|
||||
While not as convenient as buying an Embassy, this option is easier than you might imagine, and there are 4 reasons why you might prefer it:
|
||||
1. You already have a Raspberry Pi and would like to re-purpose it.
|
||||
1. You want to save on shipping costs.
|
||||
1. You prefer not to divulge your physical shipping address.
|
||||
1. You just like building things.
|
||||
|
||||
To pursue this option, follow this [guide](https://docs.start9labs.com/getting-started/diy.html).
|
||||
|
||||
## Contributing
|
||||
To build EmbassyOS from source, or to contribute to its development, see [here](https://github.com/Start9Labs/embassy-os/blob/master/CONTRIBUTING.md#building-the-image).
|
||||
@@ -3,4 +3,4 @@
|
||||
cat config/settings.yml | grep app-mgr-version-spec
|
||||
cat package.yaml | grep version
|
||||
|
||||
stack --local-bin-path ./executables build --copy-bins #--flag start9-agent:disable-auth
|
||||
stack --local-bin-path ./dist build --copy-bins #--flag start9-agent:disable-auth
|
||||
|
||||
7
agent/config/restarter.service
Normal file
7
agent/config/restarter.service
Normal file
@@ -0,0 +1,7 @@
|
||||
[Unit]
|
||||
Description=restarts dead containers
|
||||
Requires=docker.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/bin/appmgr repair-app-status
|
||||
9
agent/config/restarter.timer
Normal file
9
agent/config/restarter.timer
Normal file
@@ -0,0 +1,9 @@
|
||||
[Unit]
|
||||
Description=restarter
|
||||
|
||||
[Timer]
|
||||
OnUnitActiveSec=60s
|
||||
OnBootSec=60s
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
@@ -7,7 +7,9 @@
|
||||
/v0 ServerR GET PATCH
|
||||
|
||||
/v0/name NameR PATCH
|
||||
/v0/autoCheckUpdates AutoCheckUpdatesR PATCH
|
||||
|
||||
/v0/welcome/#Version WelcomeR POST
|
||||
/v0/specs SpecsR GET
|
||||
/v0/metrics MetricsR GET
|
||||
|
||||
@@ -37,7 +39,8 @@
|
||||
/v0/apps/#AppId/backup/restore RestoreBackupR POST
|
||||
/v0/apps/#AppId/autoconfig/#AppId AutoconfigureR POST
|
||||
|
||||
/v0/disks ListDisksR GET
|
||||
/v0/disks DisksR GET
|
||||
/v0/disks/eject EjectR POST
|
||||
|
||||
/v0/update UpdateAgentR POST
|
||||
/v0/wifi WifiR GET POST
|
||||
|
||||
@@ -28,12 +28,11 @@ detailed-logging: "_env:DETAILED_LOGGING:false"
|
||||
|
||||
# NB: If you need a numeric value (e.g. 123) to parse as a String, wrap it in single quotes (e.g. "_env:YESOD_PGPASS:'123'")
|
||||
# See https://github.com/yesodweb/yesod/wiki/Configuration#parsing-numeric-values-as-strings
|
||||
cors-override-star: "_env:CORS_OVERRIDE_STAR:"
|
||||
filesystem-base: "_env:FILESYSTEM_BASE:/"
|
||||
database:
|
||||
database: "start9_agent.sqlite3"
|
||||
poolsize: "_env:YESOD_SQLITE_POOLSIZE:10"
|
||||
|
||||
app-mgr-version-spec: "=0.2.6"
|
||||
app-mgr-version-spec: "=0.2.8"
|
||||
|
||||
#analytics: UA-YOURCODE
|
||||
|
||||
1
agent/migrations/0.2.6::0.2.7
Normal file
1
agent/migrations/0.2.6::0.2.7
Normal file
@@ -0,0 +1 @@
|
||||
SELECT TRUE;
|
||||
1
agent/migrations/0.2.7::0.2.8
Normal file
1
agent/migrations/0.2.7::0.2.8
Normal file
@@ -0,0 +1 @@
|
||||
SELECT TRUE;
|
||||
@@ -1,5 +1,5 @@
|
||||
name: ambassador-agent
|
||||
version: 0.2.6
|
||||
version: 0.2.8
|
||||
|
||||
default-extensions:
|
||||
- NoImplicitPrelude
|
||||
@@ -42,6 +42,7 @@ dependencies:
|
||||
- comonad
|
||||
- conduit
|
||||
- conduit-extra
|
||||
- connection
|
||||
- containers
|
||||
- cryptonite
|
||||
- cryptonite-conduit
|
||||
@@ -72,6 +73,7 @@ dependencies:
|
||||
- mime-types
|
||||
- monad-control
|
||||
- monad-logger
|
||||
- network
|
||||
- persistent
|
||||
- persistent-sqlite
|
||||
- persistent-template
|
||||
|
||||
@@ -67,6 +67,8 @@ import Model
|
||||
import Settings
|
||||
import Lib.Background
|
||||
import qualified Daemon.SslRenew as SSLRenew
|
||||
import Lib.Tor (newTorManager)
|
||||
import Daemon.TorHealth
|
||||
|
||||
appMain :: IO ()
|
||||
appMain = do
|
||||
@@ -106,13 +108,16 @@ makeFoundation appSettings = do
|
||||
-- subsite.
|
||||
appLogger <- newStdoutLoggerSet defaultBufSize >>= makeYesodLogger
|
||||
appHttpManager <- getGlobalManager
|
||||
appTorManager <- newTorManager (appTorSocksPort appSettings)
|
||||
appWebServerThreadId <- newIORef Nothing
|
||||
appSelfUpdateSpecification <- newEmptyMVar
|
||||
appIsUpdating <- newIORef Nothing
|
||||
appIsUpdateFailed <- newIORef Nothing
|
||||
appOsVersionLatest <- newIORef Nothing
|
||||
appBackgroundJobs <- newTVarIO (JobCache HM.empty)
|
||||
def <- getDefaultProcDevMetrics
|
||||
appProcDevMomentCache <- newIORef (now, mempty, def)
|
||||
appLastTorRestart <- newIORef now
|
||||
|
||||
-- We need a log function to create a connection pool. We need a connection
|
||||
-- pool to create our foundation. And we need our foundation to get a
|
||||
@@ -193,6 +198,10 @@ startupSequence foundation = do
|
||||
void . forkIO . forever $ forkIO (SSLRenew.renewSslLeafCert foundation) *> sleep 86_400
|
||||
withAgentVersionLog_ "SSL Renewal daemon started"
|
||||
|
||||
withAgentVersionLog_ "Initializing Tor health check loop"
|
||||
void . forkIO . forever $ forkIO (runReaderT torHealth foundation) *> sleep 300
|
||||
withAgentVersionLog_ "Tor health check loop running"
|
||||
|
||||
-- reloading avahi daemon
|
||||
-- DRAGONS! make sure this step happens AFTER system synchronization
|
||||
withAgentVersionLog_ "Publishing Agent to Avahi Daemon"
|
||||
|
||||
50
agent/src/Daemon/TorHealth.hs
Normal file
50
agent/src/Daemon/TorHealth.hs
Normal file
@@ -0,0 +1,50 @@
|
||||
{-# LANGUAGE QuasiQuotes #-}
|
||||
module Daemon.TorHealth where
|
||||
|
||||
import Startlude
|
||||
|
||||
import Data.String.Interpolate.IsString
|
||||
|
||||
import Foundation
|
||||
import Lib.SystemPaths
|
||||
import Lib.Tor
|
||||
import Yesod ( RenderRoute(renderRoute) )
|
||||
import Network.HTTP.Simple ( getResponseBody )
|
||||
import Network.HTTP.Client ( parseRequest )
|
||||
import Network.HTTP.Client ( httpLbs )
|
||||
import Data.ByteString.Lazy ( toStrict )
|
||||
import qualified UnliftIO.Exception as UnliftIO
|
||||
import Settings
|
||||
import Data.IORef ( writeIORef
|
||||
, readIORef
|
||||
)
|
||||
import Lib.SystemCtl
|
||||
|
||||
torHealth :: ReaderT AgentCtx IO ()
|
||||
torHealth = do
|
||||
settings <- asks appSettings
|
||||
host <- injectFilesystemBaseFromContext settings getAgentHiddenServiceUrl
|
||||
let url = mappend [i|http://#{host}:5959|] . fold $ mappend "/" <$> fst (renderRoute VersionR)
|
||||
response <- UnliftIO.try @_ @SomeException $ torGet (toS url)
|
||||
case response of
|
||||
Left _ -> do
|
||||
putStrLn @Text "Failed Tor health check"
|
||||
lastRestart <- asks appLastTorRestart >>= liftIO . readIORef
|
||||
cooldown <- asks $ appTorRestartCooldown . appSettings
|
||||
now <- liftIO getCurrentTime
|
||||
if now > addUTCTime cooldown lastRestart
|
||||
then do
|
||||
ec <- liftIO $ systemCtl RestartService "tor"
|
||||
case ec of
|
||||
ExitSuccess -> asks appLastTorRestart >>= liftIO . flip writeIORef now
|
||||
ExitFailure _ -> do
|
||||
putStrLn @Text "Failed to restart tor daemon after failed tor health check"
|
||||
else do
|
||||
putStrLn @Text "Failed tor healthcheck inside of cooldown window, tor will not be restarted"
|
||||
Right _ -> pure ()
|
||||
|
||||
torGet :: String -> ReaderT AgentCtx IO ByteString
|
||||
torGet url = do
|
||||
manager <- asks appTorManager
|
||||
req <- parseRequest url
|
||||
liftIO $ toStrict . getResponseBody <$> httpLbs req manager
|
||||
@@ -58,19 +58,23 @@ import Settings
|
||||
-- keep settings and values requiring initialization before your application
|
||||
-- starts running, such as database connections. Every handler will have
|
||||
-- access to the data present here.
|
||||
data OsVersionCache = OsVersionCache { osVersion :: Version, lastChecked :: UTCTime }
|
||||
|
||||
data AgentCtx = AgentCtx
|
||||
{ appSettings :: AppSettings
|
||||
, appHttpManager :: Manager
|
||||
, appTorManager :: Manager
|
||||
, appConnPool :: ConnectionPool -- ^ Database connection pool.
|
||||
, appLogger :: Logger
|
||||
, appWebServerThreadId :: IORef (Maybe ThreadId)
|
||||
, appIsUpdating :: IORef (Maybe Version)
|
||||
, appIsUpdateFailed :: IORef (Maybe S9Error)
|
||||
, appOsVersionLatest :: IORef (Maybe OsVersionCache)
|
||||
, appProcDevMomentCache :: IORef (UTCTime, ProcDevMomentStats, ProcDevMetrics)
|
||||
, appSelfUpdateSpecification :: MVar VersionRange
|
||||
, appBackgroundJobs :: TVar JobCache
|
||||
, appIconTags :: TVar (HM.HashMap AppId (Digest MD5))
|
||||
, appLastTorRestart :: IORef UTCTime
|
||||
}
|
||||
|
||||
setWebProcessThreadId :: ThreadId -> AgentCtx -> IO ()
|
||||
|
||||
@@ -68,6 +68,7 @@ import Lib.Background
|
||||
import Lib.Error
|
||||
import qualified Lib.External.AppMgr as AppMgr
|
||||
import qualified Lib.External.Registry as Reg
|
||||
import qualified Lib.External.AppManifest as AppManifest
|
||||
import Lib.IconCache
|
||||
import qualified Lib.Notifications as Notifications
|
||||
import Lib.SystemPaths
|
||||
@@ -209,6 +210,7 @@ getAvailableAppByIdLogic appId = do
|
||||
, appAvailableFullReleaseNotes = storeAppVersionInfoReleaseNotes latest
|
||||
, appAvailableFullDependencyRequirements = HM.elems dependencyRequirements
|
||||
, appAvailableFullVersions = storeAppVersionInfoVersion <$> storeAppVersions
|
||||
, appAvailableFullInstallAlert = storeAppVersionInfoInstallAlert latest
|
||||
}
|
||||
|
||||
getAppLogsByIdR :: AppId -> Handler (JSONResponse [Text])
|
||||
@@ -230,7 +232,7 @@ getInstalledAppsLogic :: (Has (Reader AgentCtx) sig m, Has AppMgr2.AppMgr sig m,
|
||||
getInstalledAppsLogic = do
|
||||
jobCache <- asks appBackgroundJobs >>= liftIO . readTVarIO
|
||||
let installCache = installInfo . fst <$> inspect SInstalling jobCache
|
||||
serverApps <- AppMgr2.list [AppMgr2.flags|-s -d|]
|
||||
serverApps <- AppMgr2.list [AppMgr2.flags|-s -d -m|]
|
||||
let remapped = remapAppMgrInfo jobCache serverApps
|
||||
installingPreviews = flip
|
||||
HM.mapWithKey
|
||||
@@ -242,6 +244,7 @@ getInstalledAppsLogic = do
|
||||
, appInstalledPreviewStatus = AppStatusTmp Installing
|
||||
, appInstalledPreviewVersionInstalled = storeAppVersionInfoVersion
|
||||
, appInstalledPreviewTorAddress = Nothing
|
||||
, appInstalledPreviewUi = False
|
||||
}
|
||||
installedPreviews = flip
|
||||
HML.mapWithKey
|
||||
@@ -251,6 +254,7 @@ getInstalledAppsLogic = do
|
||||
, appInstalledPreviewStatus = s
|
||||
, appInstalledPreviewVersionInstalled = v
|
||||
, appInstalledPreviewTorAddress = infoResTorAddress
|
||||
, appInstalledPreviewUi = AppManifest.uiAvailable infoResManifest
|
||||
}
|
||||
|
||||
pure $ HML.elems $ HML.union installingPreviews installedPreviews
|
||||
@@ -283,6 +287,8 @@ getInstalledAppByIdLogic appId = do
|
||||
, appInstalledFullLastBackup = backupTime
|
||||
, appInstalledFullTorAddress = Nothing
|
||||
, appInstalledFullConfiguredRequirements = []
|
||||
, appInstalledFullUninstallAlert = Nothing
|
||||
, appInstalledFullRestoreAlert = Nothing
|
||||
}
|
||||
serverApps <- AppMgr2.list [AppMgr2.flags|-s -d|]
|
||||
let remapped = remapAppMgrInfo jobCache serverApps
|
||||
@@ -290,6 +296,7 @@ getInstalledAppByIdLogic appId = do
|
||||
let
|
||||
installed = do
|
||||
(status, version, AppMgr2.InfoRes {..}) <- hoistMaybe (HM.lookup appId remapped)
|
||||
manifest' <- lift $ LAsync.async $ AppMgr2.infoResManifest <<$>> AppMgr2.info [AppMgr2.flags|-M|] appId
|
||||
instructions' <- lift $ LAsync.async $ AppMgr2.instructions appId
|
||||
requirements <- LAsync.runConcurrently $ flip
|
||||
HML.traverseWithKey
|
||||
@@ -309,6 +316,7 @@ getInstalledAppByIdLogic appId = do
|
||||
(HM.lookup depId installCache $> AppStatusTmp Installing)
|
||||
<|> (view _1 <$> HM.lookup depId remapped)
|
||||
pure $ dependencyInfoToDependencyRequirement (AsInstalled STrue) (base, depStatus, depInfo)
|
||||
manifest <- lift $ LAsync.wait manifest'
|
||||
instructions <- lift $ LAsync.wait instructions'
|
||||
backupTime <- lift $ LAsync.wait backupTime'
|
||||
pure AppInstalledFull { appInstalledFullBase = AppBase appId infoResTitle (iconUrl appId version)
|
||||
@@ -318,6 +326,8 @@ getInstalledAppByIdLogic appId = do
|
||||
, appInstalledFullLastBackup = backupTime
|
||||
, appInstalledFullTorAddress = infoResTorAddress
|
||||
, appInstalledFullConfiguredRequirements = HM.elems requirements
|
||||
, appInstalledFullUninstallAlert = manifest >>= AppManifest.appManifestUninstallAlert
|
||||
, appInstalledFullRestoreAlert = manifest >>= AppManifest.appManifestRestoreAlert
|
||||
}
|
||||
runMaybeT (installing <|> installed) `orThrowM` NotFoundE "appId" (show appId)
|
||||
|
||||
@@ -642,6 +652,7 @@ getAvailableAppVersionInfoLogic appId appVersionSpec = do
|
||||
pure AppVersionInfo { appVersionInfoVersion = storeAppVersionInfoVersion
|
||||
, appVersionInfoReleaseNotes = storeAppVersionInfoReleaseNotes
|
||||
, appVersionInfoDependencyRequirements = HM.elems requirements
|
||||
, appVersionInfoInstallAlert = storeAppVersionInfoInstallAlert
|
||||
}
|
||||
|
||||
postAutoconfigureR :: AppId -> AppId -> Handler (JSONResponse (WithBreakages AutoconfigureChangesRes))
|
||||
@@ -710,7 +721,6 @@ remapAppMgrInfo jobCache serverApps = flip
|
||||
$ ((, infoResVersion) <$> HM.lookup appId tmpStatuses)
|
||||
<|> (guard (not infoResIsConfigured || infoResIsRecoverable) $> (NeedsConfig, infoResVersion))
|
||||
<|> (guard realViolations $> (BrokenDependencies, infoResVersion))
|
||||
<|> (guard (infoResStatus == Restarting) $> (Crashed, infoResVersion))
|
||||
in ( status
|
||||
, version
|
||||
, infoRes
|
||||
@@ -731,6 +741,7 @@ storeAppToAvailablePreview s@StoreApp {..} installed = AppAvailablePreview
|
||||
(storeAppVersionInfoVersion $ extract storeAppVersions)
|
||||
storeAppDescriptionShort
|
||||
installed
|
||||
storeAppTimestamp
|
||||
|
||||
type AsInstalled :: Bool -> Type
|
||||
newtype AsInstalled a = AsInstalled { unAsInstalled :: SBool a }
|
||||
|
||||
@@ -57,6 +57,14 @@ instance FromJSON RestoreBackupReq where
|
||||
restoreBackupPassword <- o .:? "password" .!= Nothing
|
||||
pure RestoreBackupReq { .. }
|
||||
|
||||
data EjectDiskReq = EjectDiskReq
|
||||
{ ejectDiskLogicalName :: Text
|
||||
} deriving (Eq, Show)
|
||||
instance FromJSON EjectDiskReq where
|
||||
parseJSON = withObject "Eject Disk Req" $ \o -> do
|
||||
ejectDiskLogicalName <- o .: "logicalName"
|
||||
pure EjectDiskReq { .. }
|
||||
|
||||
-- Handlers
|
||||
|
||||
postCreateBackupR :: AppId -> Handler ()
|
||||
@@ -95,9 +103,11 @@ postRestoreBackupR appId = disableEndpointOnFailedUpdate $ do
|
||||
& handleS9ErrC
|
||||
& runM
|
||||
|
||||
getListDisksR :: Handler (JSONResponse [AppMgr.DiskInfo])
|
||||
getListDisksR = fmap JSONResponse . runM . handleS9ErrC $ listDisksLogic
|
||||
getDisksR :: Handler (JSONResponse [AppMgr.DiskInfo])
|
||||
getDisksR = fmap JSONResponse . runM . handleS9ErrC $ listDisksLogic
|
||||
|
||||
postEjectR :: Handler ()
|
||||
postEjectR = runM . handleS9ErrC $ requireCheckJsonBody >>= ejectDiskLogic . ejectDiskLogicalName
|
||||
|
||||
-- Logic
|
||||
|
||||
@@ -203,6 +213,13 @@ restoreBackupLogic appId RestoreBackupReq {..} = do
|
||||
listDisksLogic :: (Has (Error S9Error) sig m, MonadIO m) => m [AppMgr.DiskInfo]
|
||||
listDisksLogic = runExceptT AppMgr.diskShow >>= liftEither
|
||||
|
||||
ejectDiskLogic :: (Has (Error S9Error) sig m, MonadIO m) => Text -> m ()
|
||||
ejectDiskLogic t = do
|
||||
(ec, _) <- AppMgr.readProcessInheritStderr "eject" [toS t] ""
|
||||
case ec of
|
||||
ExitSuccess -> pure ()
|
||||
ExitFailure n -> throwError $ EjectE n
|
||||
|
||||
insertBackupResult :: MonadIO m => AppId -> Version -> Bool -> SqlPersistT m (Entity BackupRecord)
|
||||
insertBackupResult appId appVersion succeeded = do
|
||||
uuid <- liftIO nextRandom
|
||||
|
||||
@@ -5,26 +5,23 @@ module Handler.Hosts where
|
||||
import Startlude hiding ( ask )
|
||||
|
||||
import Control.Carrier.Lift ( runM )
|
||||
import Control.Carrier.Error.Church
|
||||
import Data.Conduit
|
||||
import qualified Data.Conduit.Binary as CB
|
||||
import Data.Time.ISO8601
|
||||
import Yesod.Core hiding ( expiresAt )
|
||||
|
||||
import Foundation
|
||||
import Daemon.ZeroConf
|
||||
import Handler.Register ( produceProofOfKey
|
||||
, checkExistingPasswordRegistration
|
||||
import Handler.Register ( checkExistingPasswordRegistration
|
||||
, getRegistration
|
||||
)
|
||||
import Handler.Types.Hosts
|
||||
import Handler.Types.Register
|
||||
import Lib.Crypto
|
||||
import Lib.Error
|
||||
import Lib.Password ( rootAccountName )
|
||||
import Lib.ProductKey
|
||||
import Lib.Ssl
|
||||
import Lib.SystemPaths
|
||||
import Lib.Tor
|
||||
import Lib.SystemPaths ( injectFilesystemBaseFromContext
|
||||
, rootCaCertPath
|
||||
, SystemPath(relativeTo)
|
||||
)
|
||||
import Settings
|
||||
|
||||
getHostsR :: Handler HostsRes
|
||||
@@ -34,7 +31,6 @@ getHostsR = handleS9ErrT $ do
|
||||
hostParams <- extractHostsQueryParams
|
||||
|
||||
verifyHmac productKey hostParams
|
||||
verifyTimestampNotExpired $ hostsParamsExpiration hostParams
|
||||
|
||||
mClaimedAt <- checkExistingPasswordRegistration rootAccountName
|
||||
case mClaimedAt of
|
||||
@@ -52,31 +48,6 @@ verifyHmac productKey params = do
|
||||
HostsParams { hostsParamsHmac, hostsParamsExpiration, hostsParamsSalt } = params
|
||||
unauthorizedHmac = ClientCryptographyE "Unauthorized hmac"
|
||||
|
||||
verifyTimestampNotExpired :: MonadIO m => Text -> S9ErrT m ()
|
||||
verifyTimestampNotExpired expirationTimestamp = do
|
||||
now <- liftIO getCurrentTime
|
||||
case parseISO8601 . toS $ expirationTimestamp of
|
||||
Nothing -> throwE $ TTLExpirationE "invalid timestamp"
|
||||
Just expiration -> when (expiration < now) (throwE $ TTLExpirationE "expired")
|
||||
|
||||
getRegistration :: (MonadIO m, HasFilesystemBase sig m, Has (Error S9Error) sig m) => Text -> UTCTime -> m RegisterRes
|
||||
getRegistration productKey registerResClaimedAt = do
|
||||
torAddress <- getAgentHiddenServiceUrlMaybe >>= \case
|
||||
Nothing -> throwError $ NotFoundE "prior registration" "torAddress"
|
||||
Just t -> pure $ t
|
||||
caCert <- readSystemPath rootCaCertPath >>= \case
|
||||
Nothing -> throwError $ NotFoundE "prior registration" "cert"
|
||||
Just t -> pure t
|
||||
|
||||
-- create an hmac of the torAddress + caCert for front end
|
||||
registerResTorAddressSig <- produceProofOfKey productKey torAddress
|
||||
registerResCertSig <- produceProofOfKey productKey caCert
|
||||
|
||||
let registerResCertName = root_CA_CERT_NAME
|
||||
registerResLanAddress <- getStart9AgentHostnameLocal
|
||||
|
||||
pure RegisterRes { .. }
|
||||
|
||||
getCertificateR :: Handler TypedContent
|
||||
getCertificateR = do
|
||||
base <- getsYesod $ appFilesystemBase . appSettings
|
||||
|
||||
@@ -5,7 +5,10 @@ module Handler.Register where
|
||||
|
||||
import Startlude hiding ( ask )
|
||||
|
||||
import Control.Carrier.Error.Either ( runError )
|
||||
import Control.Carrier.Error.Either ( runError
|
||||
, Error
|
||||
, throwError
|
||||
)
|
||||
import Control.Carrier.Lift
|
||||
import Control.Effect.Throw ( liftEither )
|
||||
import Crypto.Cipher.Types
|
||||
@@ -29,6 +32,7 @@ import Lib.Password
|
||||
import Lib.ProductKey
|
||||
import Lib.Ssl
|
||||
import Lib.SystemPaths
|
||||
import Lib.Tor
|
||||
import Model
|
||||
import Settings
|
||||
|
||||
@@ -46,8 +50,11 @@ postRegisterR = handleS9ErrT $ do
|
||||
|
||||
-- Check for existing registration.
|
||||
checkExistingPasswordRegistration rootAccountName >>= \case
|
||||
Nothing -> pure ()
|
||||
Just _ -> sendResponseStatus (Status 209 "Preexisting") ()
|
||||
Nothing -> pure ()
|
||||
Just claimedAt -> do
|
||||
res <- mapExceptT (liftIO . runM . injectFilesystemBaseFromContext settings)
|
||||
$ getRegistration productKey claimedAt
|
||||
sendResponseStatus (Status 209 "Preexisting") res
|
||||
|
||||
-- install new tor hidden service key and restart tor
|
||||
registerResTorAddress <- runM (injectFilesystemBaseFromContext settings $ bootupTor torKeyFileContents) >>= \case
|
||||
@@ -139,3 +146,21 @@ produceProofOfKey key message = do
|
||||
salt <- random16
|
||||
let hmac = computeHmac key message salt
|
||||
pure $ HmacSig hmac message salt
|
||||
|
||||
getRegistration :: (MonadIO m, HasFilesystemBase sig m, Has (Error S9Error) sig m) => Text -> UTCTime -> m RegisterRes
|
||||
getRegistration productKey registerResClaimedAt = do
|
||||
torAddress <- getAgentHiddenServiceUrlMaybe >>= \case
|
||||
Nothing -> throwError $ NotFoundE "prior registration" "torAddress"
|
||||
Just t -> pure $ t
|
||||
caCert <- readSystemPath rootCaCertPath >>= \case
|
||||
Nothing -> throwError $ NotFoundE "prior registration" "cert"
|
||||
Just t -> pure t
|
||||
|
||||
-- create an hmac of the torAddress + caCert for front end
|
||||
registerResTorAddressSig <- produceProofOfKey productKey torAddress
|
||||
registerResCertSig <- produceProofOfKey productKey caCert
|
||||
|
||||
let registerResCertName = root_CA_CERT_NAME
|
||||
registerResLanAddress <- getStart9AgentHostnameLocal
|
||||
|
||||
pure RegisterRes { .. }
|
||||
|
||||
@@ -28,6 +28,7 @@ data AppAvailablePreview = AppAvailablePreview
|
||||
, appAvailablePreviewVersionLatest :: Version
|
||||
, appAvailablePreviewDescriptionShort :: Text
|
||||
, appAvailablePreviewInstallInfo :: Maybe (Version, AppStatus)
|
||||
, appAvailablePreviewTimestamp :: UTCTime
|
||||
}
|
||||
deriving (Eq, Show)
|
||||
instance ToJSON AppAvailablePreview where
|
||||
@@ -36,6 +37,7 @@ instance ToJSON AppAvailablePreview where
|
||||
, "descriptionShort" .= appAvailablePreviewDescriptionShort
|
||||
, "versionInstalled" .= (fst <$> appAvailablePreviewInstallInfo)
|
||||
, "status" .= (snd <$> appAvailablePreviewInstallInfo)
|
||||
, "latestVersionTimestamp" .= appAvailablePreviewTimestamp
|
||||
]
|
||||
|
||||
data AppInstalledPreview = AppInstalledPreview
|
||||
@@ -43,6 +45,7 @@ data AppInstalledPreview = AppInstalledPreview
|
||||
, appInstalledPreviewStatus :: AppStatus
|
||||
, appInstalledPreviewVersionInstalled :: Version
|
||||
, appInstalledPreviewTorAddress :: Maybe TorAddress
|
||||
, appInstalledPreviewUi :: Bool
|
||||
}
|
||||
deriving (Eq, Show)
|
||||
instance ToJSON AppInstalledPreview where
|
||||
@@ -50,6 +53,7 @@ instance ToJSON AppInstalledPreview where
|
||||
[ "status" .= appInstalledPreviewStatus
|
||||
, "versionInstalled" .= appInstalledPreviewVersionInstalled
|
||||
, "torAddress" .= (unTorAddress <$> appInstalledPreviewTorAddress)
|
||||
, "ui" .= appInstalledPreviewUi
|
||||
]
|
||||
|
||||
data InstallNewAppReq = InstallNewAppReq
|
||||
@@ -70,6 +74,7 @@ data AppAvailableFull = AppAvailableFull
|
||||
, appAvailableFullDescriptionShort :: Text
|
||||
, appAvailableFullDescriptionLong :: Text
|
||||
, appAvailableFullReleaseNotes :: Text
|
||||
, appAvailableFullInstallAlert :: Maybe Text
|
||||
, appAvailableFullDependencyRequirements :: [Full AppDependencyRequirement]
|
||||
, appAvailableFullVersions :: NonEmpty Version
|
||||
}
|
||||
@@ -86,6 +91,7 @@ instance ToJSON AppAvailableFull where
|
||||
, "versions" .= appAvailableFullVersions
|
||||
, "releaseNotes" .= appAvailableFullReleaseNotes
|
||||
, "serviceRequirements" .= appAvailableFullDependencyRequirements
|
||||
, "installAlert" .= appAvailableFullInstallAlert
|
||||
]
|
||||
)
|
||||
|
||||
@@ -126,6 +132,8 @@ data AppInstalledFull = AppInstalledFull
|
||||
, appInstalledFullInstructions :: Maybe Text
|
||||
, appInstalledFullLastBackup :: Maybe UTCTime
|
||||
, appInstalledFullConfiguredRequirements :: [Stripped AppDependencyRequirement]
|
||||
, appInstalledFullUninstallAlert :: Maybe Text
|
||||
, appInstalledFullRestoreAlert :: Maybe Text
|
||||
}
|
||||
instance ToJSON AppInstalledFull where
|
||||
toJSON AppInstalledFull {..} = object
|
||||
@@ -138,18 +146,22 @@ instance ToJSON AppInstalledFull where
|
||||
, "iconURL" .= appBaseIconUrl appInstalledFullBase
|
||||
, "versionInstalled" .= appInstalledFullVersionInstalled
|
||||
, "status" .= appInstalledFullStatus
|
||||
, "uninstallAlert" .= appInstalledFullUninstallAlert
|
||||
, "restoreAlert" .= appInstalledFullRestoreAlert
|
||||
]
|
||||
|
||||
data AppVersionInfo = AppVersionInfo
|
||||
{ appVersionInfoVersion :: Version
|
||||
, appVersionInfoReleaseNotes :: Text
|
||||
, appVersionInfoDependencyRequirements :: [Full AppDependencyRequirement]
|
||||
, appVersionInfoInstallAlert :: Maybe Text
|
||||
}
|
||||
instance ToJSON AppVersionInfo where
|
||||
toJSON AppVersionInfo {..} = object
|
||||
[ "version" .= appVersionInfoVersion
|
||||
, "releaseNotes" .= appVersionInfoReleaseNotes
|
||||
, "serviceRequirements" .= appVersionInfoDependencyRequirements
|
||||
, "installAlert" .= appVersionInfoInstallAlert
|
||||
]
|
||||
|
||||
data ApiDependencyViolation
|
||||
|
||||
@@ -31,13 +31,14 @@ data ServerRes = ServerRes
|
||||
, serverStatus :: Maybe AppStatus
|
||||
, serverStatusAt :: UTCTime
|
||||
, serverVersionInstalled :: Version
|
||||
, serverNotifications :: [Entity Notification]
|
||||
, serverNotifications :: [ Entity Notification ]
|
||||
, serverWifi :: WifiList
|
||||
, serverSsh :: [SshKeyFingerprint]
|
||||
, serverSsh :: [ SshKeyFingerprint ]
|
||||
, serverAlternativeRegistryUrl :: Maybe Text
|
||||
, serverSpecs :: SpecsRes
|
||||
}
|
||||
deriving (Eq, Show)
|
||||
, serverWelcomeAck :: Bool
|
||||
, serverAutoCheckUpdates :: Bool
|
||||
} deriving (Eq, Show)
|
||||
|
||||
type JsonEncoding a = Encoding
|
||||
jsonEncode :: (Monad m, ToJSON a) => a -> m (JsonEncoding a)
|
||||
@@ -51,12 +52,13 @@ instance ToJSON ServerRes where
|
||||
Nothing -> String "UPDATING"
|
||||
Just stat -> toJSON stat
|
||||
, "versionInstalled" .= serverVersionInstalled
|
||||
, "versionLatest" .= Null
|
||||
, "notifications" .= serverNotifications
|
||||
, "wifi" .= serverWifi
|
||||
, "ssh" .= serverSsh
|
||||
, "alternativeRegistryUrl" .= serverAlternativeRegistryUrl
|
||||
, "specs" .= serverSpecs
|
||||
, "welcomeAck" .= serverWelcomeAck
|
||||
, "autoCheckUpdates" .= serverAutoCheckUpdates
|
||||
]
|
||||
instance ToTypedContent ServerRes where
|
||||
toTypedContent = toTypedContent . toJSON
|
||||
|
||||
@@ -8,7 +8,7 @@ import Control.Carrier.Lift ( runM )
|
||||
import Data.Aeson
|
||||
import Data.IORef
|
||||
import qualified Data.Text as T
|
||||
import Database.Persist
|
||||
import Database.Persist as Persist
|
||||
import Yesod.Core.Handler
|
||||
import Yesod.Persist.Core
|
||||
import Yesod.Core.Json
|
||||
@@ -30,6 +30,7 @@ import Lib.SystemPaths
|
||||
import Lib.Ssh
|
||||
import Lib.Tor
|
||||
import Lib.Types.Core
|
||||
import Lib.Types.Emver
|
||||
import Model
|
||||
import Settings
|
||||
import Util.Function
|
||||
@@ -37,7 +38,8 @@ import Util.Function
|
||||
|
||||
getServerR :: Handler (JsonEncoding ServerRes)
|
||||
getServerR = handleS9ErrT $ do
|
||||
settings <- getsYesod appSettings
|
||||
agentCtx <- getYesod
|
||||
let settings = appSettings agentCtx
|
||||
now <- liftIO getCurrentTime
|
||||
isUpdating <- getsYesod appIsUpdating >>= liftIO . readIORef
|
||||
|
||||
@@ -53,8 +55,11 @@ getServerR = handleS9ErrT $ do
|
||||
alternativeRegistryUrl <- runM $ injectFilesystemBaseFromContext settings $ readSystemPath altRegistryUrlPath
|
||||
name <- runM $ injectFilesystemBaseFromContext settings $ readSystemPath serverNamePath
|
||||
ssh <- readFromPath settings sshKeysFilePath >>= parseSshKeys
|
||||
wifi <- WpaSupplicant.runWlan0 $ liftA2 WifiList WpaSupplicant.getCurrentNetwork WpaSupplicant.listNetworks
|
||||
wifi <- WpaSupplicant.runWlan0 $ liftA2 WifiList WpaSupplicant.getCurrentNetwork WpaSupplicant.listNetworks
|
||||
specs <- getSpecs settings
|
||||
welcomeAck <- fmap isJust . lift . runDB . Persist.get $ WelcomeAckKey agentVersion
|
||||
autoCheckUpdates <- runM $ injectFilesystemBaseFromContext settings $ fmap not (existsSystemPath disableAutoCheckUpdatesPath)
|
||||
|
||||
let sid = T.drop 7 $ specsNetworkId specs
|
||||
|
||||
jsonEncode ServerRes { serverId = specsNetworkId specs
|
||||
@@ -67,6 +72,8 @@ getServerR = handleS9ErrT $ do
|
||||
, serverSsh = ssh
|
||||
, serverAlternativeRegistryUrl = alternativeRegistryUrl
|
||||
, serverSpecs = specs
|
||||
, serverWelcomeAck = welcomeAck
|
||||
, serverAutoCheckUpdates = autoCheckUpdates
|
||||
}
|
||||
where
|
||||
parseSshKeys :: Text -> S9ErrT Handler [SshKeyFingerprint]
|
||||
@@ -76,6 +83,9 @@ getServerR = handleS9ErrT $ do
|
||||
Left e -> throwE $ InvalidSshKeyE (toS e)
|
||||
Right as -> pure $ uncurry3 SshKeyFingerprint <$> as
|
||||
|
||||
postWelcomeR :: Version -> Handler ()
|
||||
postWelcomeR version = runDB $ repsert (WelcomeAckKey version) WelcomeAck
|
||||
|
||||
getSpecs :: MonadIO m => AppSettings -> S9ErrT m SpecsRes
|
||||
getSpecs settings = do
|
||||
specsCPU <- liftIO getCpuInfo
|
||||
@@ -102,9 +112,22 @@ newtype NullablePatchReq = NullablePatchReq { mpatchValue :: Maybe Text } derivi
|
||||
instance FromJSON NullablePatchReq where
|
||||
parseJSON = withObject "Nullable Patch Request" $ \o -> NullablePatchReq <$> o .:? "value"
|
||||
|
||||
newtype BoolPatchReq = BoolPatchReq { bpatchValue :: Bool } deriving (Eq, Show)
|
||||
|
||||
instance FromJSON BoolPatchReq where
|
||||
parseJSON = withObject "Patch Request" $ \o -> BoolPatchReq <$> o .: "value"
|
||||
|
||||
patchNameR :: Handler ()
|
||||
patchNameR = patchFile serverNamePath
|
||||
|
||||
patchAutoCheckUpdatesR :: Handler ()
|
||||
patchAutoCheckUpdatesR = do
|
||||
settings <- getsYesod appSettings
|
||||
BoolPatchReq val <- requireCheckJsonBody
|
||||
runM $ injectFilesystemBaseFromContext settings $ if val
|
||||
then deleteSystemPath disableAutoCheckUpdatesPath
|
||||
else writeSystemPath disableAutoCheckUpdatesPath ""
|
||||
|
||||
patchFile :: SystemPath -> Handler ()
|
||||
patchFile path = do
|
||||
settings <- getsYesod appSettings
|
||||
|
||||
@@ -25,7 +25,6 @@ import qualified Data.HashMap.Strict as HM
|
||||
import Data.Singletons.Prelude hiding ( Error )
|
||||
import Data.Singletons.Prelude.Either
|
||||
import qualified Data.String as String
|
||||
import Exinst
|
||||
|
||||
import Lib.Algebra.Domain.AppMgr.Types
|
||||
import Lib.Algebra.Domain.AppMgr.TH
|
||||
@@ -67,7 +66,7 @@ data InfoRes a = InfoRes
|
||||
(Either_ (DefaultEqSym1 'OnlyDependencies) (ElemSym1 'IncludeDependencies) a)
|
||||
(HM.HashMap AppId DependencyInfo)
|
||||
, infoResManifest
|
||||
:: Include (Either_ (DefaultEqSym1 'OnlyManifest) (ElemSym1 'IncludeManifest) a) (Some1 AppManifest)
|
||||
:: Include (Either_ (DefaultEqSym1 'OnlyManifest) (ElemSym1 'IncludeManifest) a) AppManifest
|
||||
, infoResStatus :: Include (Either_ (DefaultEqSym1 'OnlyStatus) (ElemSym1 'IncludeStatus) a) AppContainerStatus
|
||||
}
|
||||
instance SingI (a :: Either OnlyInfoFlag [IncludeInfoFlag]) => FromJSON (InfoRes a) where
|
||||
|
||||
@@ -31,6 +31,7 @@ data S9Error =
|
||||
| AppMgrParseE Text Text String
|
||||
| AppMgrInvalidConfigE Text
|
||||
| AppMgrE Text Int
|
||||
| EjectE Int
|
||||
| AvahiE Text
|
||||
| MetricE Text
|
||||
| AppMgrVersionE Version VersionRange
|
||||
@@ -51,6 +52,7 @@ data S9Error =
|
||||
| WifiOrphaningE
|
||||
| NoPasswordExistsE
|
||||
| HostsParamsE Text
|
||||
| ParamsE Text
|
||||
| MissingFileE SystemPath
|
||||
| ClientCryptographyE Text
|
||||
| TTLExpirationE Text
|
||||
@@ -86,6 +88,7 @@ toError = \case
|
||||
AppMgrParseE cmd result e ->
|
||||
ErrorResponse APPMGR_PARSE_ERROR [i|"appmgr #{cmd}" yielded an unparseable result:#{result}\nError: #{e}|]
|
||||
AppMgrE cmd code -> ErrorResponse APPMGR_ERROR [i|"appmgr #{cmd}" exited with #{code}|]
|
||||
EjectE code -> ErrorResponse EJECT_ERROR [i|"eject" command exited with #{code}|]
|
||||
AppMgrVersionE av avs ->
|
||||
ErrorResponse APPMGR_ERROR [i|"appmgr version #{av}" fails to satisfy requisite spec #{avs}|]
|
||||
AvahiE e -> ErrorResponse AVAHI_ERROR [i|#{e}|]
|
||||
@@ -136,6 +139,7 @@ toError = \case
|
||||
TTLExpirationE desc -> ErrorResponse REGISTRATION_ERROR [i|TTL Expiration failure: #{desc}|]
|
||||
EnvironmentValE appId -> ErrorResponse SYNCHRONIZATION_ERROR [i|Could not read environment values for #{appId}|]
|
||||
HostsParamsE key -> ErrorResponse REGISTRATION_ERROR [i|Missing or invalid parameter #{key}|]
|
||||
ParamsE key -> ErrorResponse INVALID_REQUEST [i|Missing or invalid parameter #{key}|]
|
||||
InternalE msg -> ErrorResponse INTERNAL_ERROR msg
|
||||
BackupE appId reason -> ErrorResponse BACKUP_ERROR [i|Backup failed for #{appId}: #{reason}|]
|
||||
BackupPassInvalidE -> ErrorResponse BACKUP_ERROR [i|Password provided for backups is invalid|]
|
||||
@@ -151,6 +155,7 @@ data ErrorCode =
|
||||
| APPMGR_CONFIG_ERROR
|
||||
| APPMGR_PARSE_ERROR
|
||||
| APPMGR_ERROR
|
||||
| EJECT_ERROR
|
||||
| AVAHI_ERROR
|
||||
| REGISTRY_ERROR
|
||||
| APP_NOT_INSTALLED
|
||||
@@ -201,6 +206,7 @@ toStatus = \case
|
||||
AppMgrParseE{} -> status500
|
||||
AppMgrInvalidConfigE _ -> status400
|
||||
AppMgrE _ _ -> status500
|
||||
EjectE _ -> status500
|
||||
AppMgrVersionE _ _ -> status500
|
||||
AvahiE _ -> status500
|
||||
MetricE _ -> status500
|
||||
@@ -238,6 +244,7 @@ toStatus = \case
|
||||
TTLExpirationE _ -> status403
|
||||
EnvironmentValE _ -> status500
|
||||
HostsParamsE _ -> status400
|
||||
ParamsE _ -> status400
|
||||
BackupE _ _ -> status500
|
||||
BackupPassInvalidE -> status403
|
||||
InternalE _ -> status500
|
||||
|
||||
77
agent/src/Lib/External/AppManifest.hs
vendored
77
agent/src/Lib/External/AppManifest.hs
vendored
@@ -6,10 +6,8 @@ import Startlude hiding ( ask )
|
||||
|
||||
import Control.Effect.Reader.Labelled
|
||||
import Data.Aeson
|
||||
import Data.Singletons.TypeLits
|
||||
import qualified Data.HashMap.Strict as HM
|
||||
import qualified Data.Yaml as Yaml
|
||||
import Exinst
|
||||
|
||||
import Lib.Error
|
||||
import Lib.SystemPaths
|
||||
@@ -49,52 +47,49 @@ instance FromJSON AssetMapping where
|
||||
assetMappingOverwrite <- o .: "overwrite"
|
||||
pure $ AssetMapping { .. }
|
||||
|
||||
data AppManifest (n :: Nat) where
|
||||
AppManifestV0 ::{ appManifestV0Id :: AppId
|
||||
, appManifestV0Version :: Version
|
||||
, appManifestV0Title :: Text
|
||||
, appManifestV0DescShort :: Text
|
||||
, appManifestV0DescLong :: Text
|
||||
, appManifestV0ReleaseNotes :: Text
|
||||
, appManifestV0PortMapping :: HM.HashMap Word16 Word16
|
||||
, appManifestV0ImageType :: ImageType
|
||||
, appManifestV0Mount :: FilePath
|
||||
, appManifestV0Assets :: [AssetMapping]
|
||||
, appManifestV0OnionVersion :: OnionVersion
|
||||
, appManifestV0Dependencies :: HM.HashMap AppId VersionRange
|
||||
} -> AppManifest 0
|
||||
data AppManifest where
|
||||
AppManifest ::{ appManifestId :: AppId
|
||||
, appManifestVersion :: Version
|
||||
, appManifestTitle :: Text
|
||||
, appManifestDescShort :: Text
|
||||
, appManifestDescLong :: Text
|
||||
, appManifestReleaseNotes :: Text
|
||||
, appManifestPortMapping :: HM.HashMap Word16 Word16
|
||||
, appManifestImageType :: ImageType
|
||||
, appManifestMount :: FilePath
|
||||
, appManifestAssets :: [AssetMapping]
|
||||
, appManifestOnionVersion :: OnionVersion
|
||||
, appManifestDependencies :: HM.HashMap AppId VersionRange
|
||||
, appManifestUninstallAlert :: Maybe Text
|
||||
, appManifestRestoreAlert :: Maybe Text
|
||||
} -> AppManifest
|
||||
|
||||
instance FromJSON (Some1 AppManifest) where
|
||||
parseJSON = withObject "App Manifest" $ \o -> do
|
||||
o .: "compat" >>= \case
|
||||
("v0" :: Text) -> Some1 (SNat @0) <$> parseJSON (Object o)
|
||||
compat -> fail $ "Unknown Manifest Version: " <> toS compat
|
||||
uiAvailable :: AppManifest -> Bool
|
||||
uiAvailable AppManifest {..} = isJust $ HM.lookup 80 appManifestPortMapping
|
||||
|
||||
instance FromJSON (AppManifest 0) where
|
||||
parseJSON = withObject "App Manifest V0" $ \o -> do
|
||||
appManifestV0Id <- o .: "id"
|
||||
appManifestV0Version <- o .: "version"
|
||||
appManifestV0Title <- o .: "title"
|
||||
appManifestV0DescShort <- o .: "description" >>= (.: "short")
|
||||
appManifestV0DescLong <- o .: "description" >>= (.: "long")
|
||||
appManifestV0ReleaseNotes <- o .: "release-notes"
|
||||
appManifestV0PortMapping <- o .: "ports" >>= fmap HM.fromList . traverse parsePortMapping
|
||||
appManifestV0ImageType <- o .: "image" >>= (.: "type")
|
||||
appManifestV0Mount <- o .: "mount"
|
||||
appManifestV0Assets <- o .: "assets" >>= traverse parseJSON
|
||||
appManifestV0OnionVersion <- o .: "hidden-service-version"
|
||||
appManifestV0Dependencies <- o .:? "dependencies" .!= HM.empty >>= traverse parseDepInfo
|
||||
pure $ AppManifestV0 { .. }
|
||||
instance FromJSON AppManifest where
|
||||
parseJSON = withObject "App Manifest " $ \o -> do
|
||||
appManifestId <- o .: "id"
|
||||
appManifestVersion <- o .: "version"
|
||||
appManifestTitle <- o .: "title"
|
||||
appManifestDescShort <- o .: "description" >>= (.: "short")
|
||||
appManifestDescLong <- o .: "description" >>= (.: "long")
|
||||
appManifestReleaseNotes <- o .: "release-notes"
|
||||
appManifestPortMapping <- o .: "ports" >>= fmap HM.fromList . traverse parsePortMapping
|
||||
appManifestImageType <- o .: "image" >>= (.: "type")
|
||||
appManifestMount <- o .: "mount"
|
||||
appManifestAssets <- o .: "assets" >>= traverse parseJSON
|
||||
appManifestOnionVersion <- o .: "hidden-service-version"
|
||||
appManifestDependencies <- o .:? "dependencies" .!= HM.empty >>= traverse parseDepInfo
|
||||
appManifestUninstallAlert <- o .:? "uninstall-alert"
|
||||
appManifestRestoreAlert <- o .:? "restore-alert"
|
||||
pure $ AppManifest { .. }
|
||||
where
|
||||
parsePortMapping = withObject "Port Mapping" $ \o -> liftA2 (,) (o .: "tor") (o .: "internal")
|
||||
parseDepInfo = withObject "Dep Info" $ (.: "version")
|
||||
|
||||
getAppManifest :: (MonadIO m, HasFilesystemBase sig m) => AppId -> S9ErrT m (Maybe (Some1 AppManifest))
|
||||
getAppManifest :: (MonadIO m, HasFilesystemBase sig m) => AppId -> S9ErrT m (Maybe AppManifest)
|
||||
getAppManifest appId = do
|
||||
base <- ask @"filesystemBase"
|
||||
ExceptT $ first (ManifestParseE appId) <$> liftIO
|
||||
(Yaml.decodeFileEither . toS $ (appMgrAppPath appId <> "manifest.yaml") `relativeTo` base)
|
||||
|
||||
uiAvailable :: AppManifest n -> Bool
|
||||
uiAvailable = \case
|
||||
AppManifestV0 { appManifestV0PortMapping } -> elem 80 (HM.keys appManifestV0PortMapping)
|
||||
|
||||
2
agent/src/Lib/External/Registry.hs
vendored
2
agent/src/Lib/External/Registry.hs
vendored
@@ -36,6 +36,7 @@ import Lib.SystemPaths
|
||||
import Lib.Types.Core
|
||||
import Lib.Types.Emver
|
||||
import Lib.Types.ServerApp
|
||||
import Data.Time.ISO8601 ( parseISO8601 )
|
||||
|
||||
newtype AppManifestRes = AppManifestRes
|
||||
{ storeApps :: [StoreApp] } deriving (Eq, Show)
|
||||
@@ -135,6 +136,7 @@ parseAppData = do
|
||||
storeAppVersions <- ad .: "version-info" >>= \case
|
||||
[] -> fail "No Valid Version Info"
|
||||
(x : xs) -> pure $ x :| xs
|
||||
storeAppTimestamp <- ad .: "timestamp" >>= maybe (fail "Invalid ISO8601 Timestamp") pure . parseISO8601
|
||||
pure StoreApp { .. }
|
||||
|
||||
getAppVersionForSpec :: (Has RegistryUrl sig m, Has (Error S9Error) sig m, MonadIO m)
|
||||
|
||||
@@ -41,6 +41,7 @@ import System.FilePath.Posix ( takeDirectory )
|
||||
import System.Directory
|
||||
import System.IO.Error
|
||||
import System.Posix.Files
|
||||
import System.Process ( callCommand )
|
||||
import qualified Streaming.Prelude as Stream
|
||||
import qualified Streaming.Conduit as Conduit
|
||||
import qualified Streaming.Zip as Stream
|
||||
@@ -95,12 +96,12 @@ parseKernelVersion = do
|
||||
pure $ KernelVersion (Version (major', minor', patch', 0)) arch
|
||||
|
||||
synchronizer :: Synchronizer
|
||||
synchronizer = sync_0_2_6
|
||||
synchronizer = sync_0_2_8
|
||||
{-# INLINE synchronizer #-}
|
||||
|
||||
sync_0_2_6 :: Synchronizer
|
||||
sync_0_2_6 = Synchronizer
|
||||
"0.2.6"
|
||||
sync_0_2_8 :: Synchronizer
|
||||
sync_0_2_8 = Synchronizer
|
||||
"0.2.8"
|
||||
[ syncCreateAgentTmp
|
||||
, syncCreateSshDir
|
||||
, syncRemoveAvahiSystemdDependency
|
||||
@@ -119,6 +120,8 @@ sync_0_2_6 = Synchronizer
|
||||
, syncPrepSslIntermediateCaDir
|
||||
, syncPersistLogs
|
||||
, syncConvertEcdsaCerts
|
||||
, syncRestarterService
|
||||
, syncInstallEject
|
||||
]
|
||||
|
||||
syncCreateAgentTmp :: SyncOp
|
||||
@@ -167,8 +170,8 @@ syncFullUpgrade = SyncOp "Full Upgrade" check migrate True
|
||||
Just (Done _ (KernelVersion (Version av) _)) -> if av < (4, 19, 118, 0) then pure True else pure False
|
||||
_ -> pure False
|
||||
migrate = liftIO . run $ do
|
||||
shell "apt update"
|
||||
shell "apt full-upgrade -y"
|
||||
shell "apt-get update"
|
||||
shell "apt-get full-upgrade -y"
|
||||
|
||||
sync32BitKernel :: SyncOp
|
||||
sync32BitKernel = SyncOp "32 Bit Kernel Switch" check migrate True
|
||||
@@ -192,16 +195,24 @@ syncInstallNginx = SyncOp "Install Nginx" check migrate False
|
||||
where
|
||||
check = liftIO . run $ fmap isNothing (shell [i|which nginx || true|] $| conduit await)
|
||||
migrate = liftIO . run $ do
|
||||
apt "update"
|
||||
apt "install" "nginx" "-y"
|
||||
shell "apt-get update"
|
||||
shell "apt-get install nginx -y"
|
||||
|
||||
syncInstallEject :: SyncOp
|
||||
syncInstallEject = SyncOp "Install Eject" check migrate False
|
||||
where
|
||||
check = liftIO . run $ fmap isNothing (shell [i|which eject || true|] $| conduit await)
|
||||
migrate = liftIO . run $ do
|
||||
shell "apt-get update"
|
||||
shell "apt-get install eject -y"
|
||||
|
||||
syncInstallDuplicity :: SyncOp
|
||||
syncInstallDuplicity = SyncOp "Install duplicity" check migrate False
|
||||
where
|
||||
check = liftIO . run $ fmap isNothing (shell [i|which duplicity || true|] $| conduit await)
|
||||
migrate = liftIO . run $ do
|
||||
apt "update"
|
||||
apt "install" "-y" "duplicity"
|
||||
shell "apt-get update"
|
||||
shell "apt-get install -y duplicity"
|
||||
|
||||
syncInstallExfatFuse :: SyncOp
|
||||
syncInstallExfatFuse = SyncOp "Install exfat-fuse" check migrate False
|
||||
@@ -213,8 +224,8 @@ syncInstallExfatFuse = SyncOp "Install exfat-fuse" check migrate False
|
||||
ProcessException _ (ExitFailure 1) -> pure True
|
||||
_ -> throwIO e
|
||||
migrate = liftIO . run $ do
|
||||
apt "update"
|
||||
apt "install" "-y" "exfat-fuse"
|
||||
shell "apt-get update"
|
||||
shell "apt-get install -y exfat-fuse"
|
||||
|
||||
syncInstallExfatUtils :: SyncOp
|
||||
syncInstallExfatUtils = SyncOp "Install exfat-utils" check migrate False
|
||||
@@ -226,8 +237,8 @@ syncInstallExfatUtils = SyncOp "Install exfat-utils" check migrate False
|
||||
ProcessException _ (ExitFailure 1) -> pure True
|
||||
_ -> throwIO e
|
||||
migrate = liftIO . run $ do
|
||||
apt "update"
|
||||
apt "install" "-y" "exfat-utils"
|
||||
shell "apt-get update"
|
||||
shell "apt-get install -y exfat-utils"
|
||||
|
||||
syncWriteConf :: Text -> ByteString -> SystemPath -> SyncOp
|
||||
syncWriteConf name contents' confLocation = SyncOp [i|Write #{name} Conf|] check migrate False
|
||||
@@ -536,6 +547,22 @@ replaceDerivativeCerts = do
|
||||
liftIO $ renameDirectory sslDirTmp sslDir
|
||||
liftIO $ systemCtl RestartService "nginx" $> ()
|
||||
|
||||
syncRestarterService :: SyncOp
|
||||
syncRestarterService = SyncOp "Install Restarter Service" check migrate True
|
||||
where
|
||||
wantedService = $(embedFile "config/restarter.service")
|
||||
wantedTimer = $(embedFile "config/restarter.timer")
|
||||
check = do
|
||||
base <- asks $ appFilesystemBase . appSettings
|
||||
liftIO $ not <$> doesPathExist
|
||||
(toS $ "/etc/systemd/system/timers.target.wants/restarter.timer" `relativeTo` base)
|
||||
migrate = do
|
||||
base <- asks $ appFilesystemBase . appSettings
|
||||
liftIO $ BS.writeFile (toS $ "/etc/systemd/system/restarter.service" `relativeTo` base) wantedService
|
||||
liftIO $ BS.writeFile (toS $ "/etc/systemd/system/restarter.timer" `relativeTo` base) wantedTimer
|
||||
liftIO $ callCommand "systemctl enable restarter.service"
|
||||
liftIO $ callCommand "systemctl enable restarter.timer"
|
||||
|
||||
failUpdate :: S9Error -> ExceptT Void (ReaderT AgentCtx IO) ()
|
||||
failUpdate e = do
|
||||
ref <- asks appIsUpdateFailed
|
||||
|
||||
@@ -80,6 +80,11 @@ readSystemPath path = do
|
||||
$ (Just <$> readFile (toS loadPath))
|
||||
`catch` (\(e :: IOException) -> if isDoesNotExistError e then pure Nothing else throwIO e)
|
||||
|
||||
existsSystemPath :: (HasFilesystemBase sig m, MonadIO m) => SystemPath -> m Bool
|
||||
existsSystemPath path = do
|
||||
checkPath <- getAbsoluteLocationFor path
|
||||
liftIO . doesPathExist $ toS checkPath
|
||||
|
||||
-- like the above, but throws IO error if file not found
|
||||
readSystemPath' :: (HasFilesystemBase sig m, MonadIO m) => SystemPath -> m Text
|
||||
readSystemPath' path = do
|
||||
@@ -188,6 +193,9 @@ agentTorHiddenServicePrivateKeyPath = agentTorHiddenServiceDirectory <> "/hs_ed2
|
||||
serverNamePath :: SystemPath
|
||||
serverNamePath = "/root/agent/name.txt"
|
||||
|
||||
disableAutoCheckUpdatesPath :: SystemPath
|
||||
disableAutoCheckUpdatesPath = "/root/agent/.disableAutoCheckUpdates"
|
||||
|
||||
altRegistryUrlPath :: SystemPath
|
||||
altRegistryUrlPath = "/root/agent/alt_registry_url.txt"
|
||||
|
||||
|
||||
@@ -3,11 +3,22 @@ module Lib.Tor where
|
||||
import Startlude
|
||||
|
||||
import qualified Data.Text as T
|
||||
import Network.HTTP.Client
|
||||
import Network.Connection
|
||||
|
||||
import Lib.SystemPaths
|
||||
import Network.HTTP.Client.TLS ( mkManagerSettings
|
||||
, newTlsManagerWith
|
||||
)
|
||||
import Data.Default
|
||||
|
||||
getAgentHiddenServiceUrl :: (HasFilesystemBase sig m, MonadIO m) => m Text
|
||||
getAgentHiddenServiceUrl = T.strip <$> readSystemPath' agentTorHiddenServiceHostnamePath
|
||||
|
||||
getAgentHiddenServiceUrlMaybe :: (HasFilesystemBase sig m, MonadIO m) => m (Maybe Text)
|
||||
getAgentHiddenServiceUrlMaybe = fmap T.strip <$> readSystemPath agentTorHiddenServiceHostnamePath
|
||||
|
||||
-- | 'newTorManager' currently assumes the tor client lives on the localhost. The port comes in over an argument.
|
||||
-- If this is insufficient in the future, feel free to parameterize the host.
|
||||
newTorManager :: Word16 -> IO Manager
|
||||
newTorManager = newManager . mkManagerSettings def . Just . SockSettingsSimple "127.0.0.1" . fromIntegral
|
||||
|
||||
@@ -56,6 +56,10 @@ instance Show Version where
|
||||
let postfix = if q == 0 then "" else '.' : show q in show x <> "." <> show y <> "." <> show z <> postfix
|
||||
instance IsString Version where
|
||||
fromString s = either error id $ Atto.parseOnly parseVersion (T.pack s)
|
||||
instance Read Version where
|
||||
readsPrec _ s = case Atto.parseOnly parseVersion (T.pack s) of
|
||||
Left _ -> []
|
||||
Right a -> [(a, "")]
|
||||
|
||||
-- | A change in the value found at 'major' implies a breaking change in the API that this version number describes
|
||||
major :: Version -> Word
|
||||
|
||||
@@ -3,16 +3,17 @@ module Lib.Types.Emver.Orphans where
|
||||
|
||||
import Startlude
|
||||
|
||||
import Control.Monad.Fail
|
||||
import Data.Aeson
|
||||
|
||||
import Lib.Types.Emver
|
||||
import qualified Data.Attoparsec.Text as Atto
|
||||
import qualified Data.Text as T
|
||||
import Database.Persist
|
||||
import Database.Persist.Sql
|
||||
import qualified Data.Attoparsec.Text as Atto
|
||||
import Control.Monad.Fail
|
||||
import qualified Data.Text as T
|
||||
import Web.HttpApiData
|
||||
import Yesod.Core.Dispatch
|
||||
|
||||
import Lib.Types.Emver
|
||||
|
||||
instance ToJSON Version where
|
||||
toJSON = String . show
|
||||
instance FromJSON Version where
|
||||
@@ -31,9 +32,16 @@ instance FromJSON VersionRange where
|
||||
instance PersistField Version where
|
||||
toPersistValue = toPersistValue @Text . show
|
||||
fromPersistValue = first T.pack . Atto.parseOnly parseVersion <=< fromPersistValue
|
||||
|
||||
instance PersistFieldSql Version where
|
||||
sqlType _ = SqlString
|
||||
instance FromHttpApiData Version where
|
||||
parseUrlPiece = first toS . Atto.parseOnly parseVersion
|
||||
instance ToHttpApiData Version where
|
||||
toUrlPiece = show
|
||||
|
||||
instance PathPiece Version where
|
||||
toPathPiece = show
|
||||
fromPathPiece = hush . Atto.parseOnly parseVersion
|
||||
|
||||
instance PathPiece VersionRange where
|
||||
toPathPiece = show
|
||||
|
||||
@@ -20,12 +20,14 @@ data StoreApp = StoreApp
|
||||
, storeAppDescriptionLong :: Text
|
||||
, storeAppIconUrl :: Text
|
||||
, storeAppVersions :: NonEmpty StoreAppVersionInfo
|
||||
, storeAppTimestamp :: UTCTime
|
||||
}
|
||||
deriving (Eq, Show)
|
||||
|
||||
data StoreAppVersionInfo = StoreAppVersionInfo
|
||||
{ storeAppVersionInfoVersion :: Version
|
||||
, storeAppVersionInfoReleaseNotes :: Text
|
||||
, storeAppVersionInfoInstallAlert :: Maybe Text
|
||||
}
|
||||
deriving (Eq, Show)
|
||||
instance Ord StoreAppVersionInfo where
|
||||
@@ -34,6 +36,7 @@ instance FromJSON StoreAppVersionInfo where
|
||||
parseJSON = withObject "Store App Version Info" $ \o -> do
|
||||
storeAppVersionInfoVersion <- o .: "version"
|
||||
storeAppVersionInfoReleaseNotes <- o .: "release-notes"
|
||||
storeAppVersionInfoInstallAlert <- o .:? "install-alert"
|
||||
pure StoreAppVersionInfo { .. }
|
||||
instance ToJSON StoreAppVersionInfo where
|
||||
toJSON StoreAppVersionInfo {..} =
|
||||
|
||||
@@ -55,6 +55,8 @@ import Handler.Status
|
||||
import Handler.Wifi
|
||||
import Handler.V0
|
||||
import Settings
|
||||
import Network.HTTP.Types.Header ( hOrigin )
|
||||
import Data.List (lookup)
|
||||
|
||||
-- This line actually creates our YesodDispatch instance. It is the second half
|
||||
-- of the call to mkYesodData which occurs in Foundation.hs. Please see the
|
||||
@@ -64,20 +66,11 @@ mkYesodDispatch "AgentCtx" resourcesAgentCtx
|
||||
instance YesodSubDispatch Auth AgentCtx where
|
||||
yesodSubDispatch = $(mkYesodSubDispatch resourcesAuth)
|
||||
|
||||
-- | Convert our foundation to a WAI Application by calling @toWaiAppPlain@ and
|
||||
-- applying some additional middlewares.
|
||||
makeApplication :: AgentCtx -> IO Application
|
||||
makeApplication foundation = do
|
||||
logWare <- makeLogWare foundation
|
||||
-- Create the WAI application and apply middlewares
|
||||
appPlain <- toWaiAppPlain foundation
|
||||
let origin = case appCorsOverrideStar $ appSettings foundation of
|
||||
Nothing -> Nothing
|
||||
Just override -> Just ([encodeUtf8 override], True)
|
||||
pure . logWare . cors (const . Just $ policy origin) . defaultMiddlewaresNoLogging $ appPlain
|
||||
dynamicCorsResourcePolicy :: Request -> Maybe CorsResourcePolicy
|
||||
dynamicCorsResourcePolicy req = Just . policy . lookup hOrigin $ requestHeaders req
|
||||
where
|
||||
policy o = simpleCorsResourcePolicy
|
||||
{ corsOrigins = o
|
||||
{ corsOrigins = (\o' -> ([o'], True)) <$> o
|
||||
, corsMethods = ["GET", "POST", "HEAD", "PUT", "DELETE", "TRACE", "CONNECT", "OPTIONS", "PATCH"]
|
||||
, corsRequestHeaders = [ "app-version"
|
||||
, "Accept"
|
||||
@@ -138,6 +131,15 @@ makeApplication foundation = do
|
||||
, corsIgnoreFailures = True
|
||||
}
|
||||
|
||||
-- | Convert our foundation to a WAI Application by calling @toWaiAppPlain@ and
|
||||
-- applying some additional middlewares.
|
||||
makeApplication :: AgentCtx -> IO Application
|
||||
makeApplication foundation = do
|
||||
logWare <- makeLogWare foundation
|
||||
-- Create the WAI application and apply middlewares
|
||||
appPlain <- toWaiAppPlain foundation
|
||||
pure . logWare . cors dynamicCorsResourcePolicy . defaultMiddlewaresNoLogging $ appPlain
|
||||
|
||||
startWeb :: AgentCtx -> IO ()
|
||||
startWeb foundation = do
|
||||
app <- makeApplication foundation
|
||||
|
||||
@@ -59,4 +59,7 @@ BackupRecord sql=backup
|
||||
IconDigest
|
||||
Id AppId
|
||||
tag (Digest MD5)
|
||||
|
||||
WelcomeAck
|
||||
Id Version
|
||||
|]
|
||||
|
||||
@@ -41,7 +41,9 @@ data AppSettings = AppSettings
|
||||
-- ^ Should all log messages be displayed?
|
||||
, appMgrVersionSpec :: VersionRange
|
||||
, appFilesystemBase :: Text
|
||||
, appCorsOverrideStar :: Maybe Text
|
||||
, appTorSocksPort :: Word16
|
||||
-- ^ Port on localhost where the tor client is listening, defaults to 9050
|
||||
, appTorRestartCooldown :: NominalDiffTime
|
||||
}
|
||||
deriving Show
|
||||
|
||||
@@ -64,7 +66,8 @@ instance FromJSON AppSettings where
|
||||
|
||||
appMgrVersionSpec <- o .: "app-mgr-version-spec"
|
||||
appFilesystemBase <- o .: "filesystem-base"
|
||||
appCorsOverrideStar <- o .:? "cors-override-star"
|
||||
appTorSocksPort <- o .:? "tor-socks-port" .!= 9050
|
||||
appTorRestartCooldown <- o .:? "tor-restart-cooldown" .!= (secondsToNominalDiffTime 600)
|
||||
return AppSettings { .. }
|
||||
|
||||
-- | Raw bytes at compile time of @config/settings.yml@
|
||||
|
||||
@@ -69,12 +69,12 @@ instance MonadResource m => MonadResource (FE.ReaderC r m) where
|
||||
instance MonadResource m => MonadResource (FE.ErrorC e m) where
|
||||
liftResourceT = lift . liftResourceT
|
||||
|
||||
|
||||
instance MonadThrow (sub m) => MonadThrow (FE.Labelled label sub m) where
|
||||
throwM = FE.Labelled . throwM
|
||||
instance MonadThrow m => MonadThrow (FE.LiftC m) where
|
||||
throwM = FE.LiftC . throwM
|
||||
|
||||
instance MonadLogger m => MonadLogger (FE.ErrorC e m) where
|
||||
instance MonadLogger m => MonadLogger (FE.LiftC m) where
|
||||
instance MonadLogger (sub m) => MonadLogger (FE.Labelled label sub m) where
|
||||
monadLoggerLog a b c d = FE.Labelled $ monadLoggerLog a b c d
|
||||
@@ -91,6 +91,13 @@ instance MonadHandler (sub m) => MonadHandler (FE.Labelled label sub m) where
|
||||
liftHandler = FE.Labelled . liftHandler
|
||||
liftSubHandler = FE.Labelled . liftSubHandler
|
||||
|
||||
|
||||
instance MonadHandler m => MonadHandler (FE.ErrorC e m) where
|
||||
type HandlerSite (FE.ErrorC e m) = HandlerSite m
|
||||
type SubHandlerSite (FE.ErrorC e m) = SubHandlerSite m
|
||||
liftHandler = lift . liftHandler
|
||||
liftSubHandler = lift . liftSubHandler
|
||||
|
||||
instance MonadTransControl t => MonadTransControl (FE.Labelled k t) where
|
||||
type StT (FE.Labelled k t) a = StT t a
|
||||
liftWith f = FE.Labelled $ liftWith $ \run -> f (run . FE.runLabelled)
|
||||
|
||||
30
appmgr/.github/workflows/rust.yml
vendored
30
appmgr/.github/workflows/rust.yml
vendored
@@ -1,30 +0,0 @@
|
||||
name: Rust
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Cache .cargo
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cargo
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
${{ runner.os }}-cargo-
|
||||
- name: Cache target release directory
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: target/release
|
||||
key: ${{ runner.os }}-target-release-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-target-release-${{ hashFiles('**/Cargo.lock') }}
|
||||
${{ runner.os }}-target-release-
|
||||
- name: Check
|
||||
run: cargo check
|
||||
- name: Test
|
||||
run: cargo test --release
|
||||
372
appmgr/Cargo.lock
generated
372
appmgr/Cargo.lock
generated
@@ -35,7 +35,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "appmgr"
|
||||
version = "0.2.6"
|
||||
version = "0.2.8"
|
||||
dependencies = [
|
||||
"argonautica",
|
||||
"async-trait",
|
||||
@@ -45,12 +45,13 @@ dependencies = [
|
||||
"emver",
|
||||
"failure",
|
||||
"file-lock",
|
||||
"futures 0.3.7",
|
||||
"futures 0.3.8",
|
||||
"git-version",
|
||||
"itertools 0.9.0",
|
||||
"lazy_static",
|
||||
"linear-map",
|
||||
"log",
|
||||
"nix 0.19.1",
|
||||
"openssl",
|
||||
"pest",
|
||||
"pest_derive",
|
||||
@@ -64,7 +65,8 @@ dependencies = [
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"simple-logging",
|
||||
"tokio",
|
||||
"tokio 0.3.5",
|
||||
"tokio-compat-02",
|
||||
"tokio-tar",
|
||||
]
|
||||
|
||||
@@ -105,9 +107,9 @@ checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.41"
|
||||
version = "0.1.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b246867b8b3b6ae56035f1eb1ed557c1d8eae97f0d53696138a50fa0e3a3b8c0"
|
||||
checksum = "8d3a45e77e34375a7923b1e8febb049bb011f064714a8e17a1a616fef01da13d"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.24",
|
||||
"quote 1.0.7",
|
||||
@@ -172,6 +174,12 @@ version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
|
||||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
version = "0.48.1"
|
||||
@@ -289,6 +297,12 @@ version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0dcbc35f504eb6fc275a6d20e4ebcda18cf50d40ba6fabff8c711fa16cb3b16"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.61"
|
||||
@@ -354,6 +368,25 @@ dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cloudabi"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4344512281c643ae7638bbabc3af17a11307803ec8f0fcad9fae512a8bf36467"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "console_error_panic_hook"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8d976903543e0c48546a91908f21588a680a8c8f984df9a5d69feccb2b2a211"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "constant_time_eq"
|
||||
version = "0.1.5"
|
||||
@@ -495,7 +528,8 @@ checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
|
||||
[[package]]
|
||||
name = "emver"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/Start9Labs/emver-rs.git#9007920a8e361669fb83b29dd8506b32eeb20180"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7621f4df0519152a2ceb5f8cc0685e9fa6c8b7aec289e1afb37c66849cb71ffe"
|
||||
dependencies = [
|
||||
"either",
|
||||
"fp-core",
|
||||
@@ -568,7 +602,7 @@ dependencies = [
|
||||
"gcc",
|
||||
"libc",
|
||||
"mktemp",
|
||||
"nix",
|
||||
"nix 0.11.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -579,7 +613,7 @@ checksum = "3ed85775dcc68644b5c950ac06a2b23768d3bc9390464151aaf27136998dcf9e"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"redox_syscall 0.1.57",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
@@ -604,6 +638,16 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ece68d15c92e84fa4f19d3780f1294e5ca82a78a6d515f1efaabcc144688be00"
|
||||
dependencies = [
|
||||
"matches",
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fp-core"
|
||||
version = "0.1.9"
|
||||
@@ -649,9 +693,9 @@ checksum = "4c7e4c2612746b0df8fed4ce0c69156021b704c9aefa360311c04e6e9e002eed"
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.7"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95314d38584ffbfda215621d723e0a3906f032e03ae5551e650058dac83d4797"
|
||||
checksum = "9b3b0c040a1fe6529d30b3c5944b280c7f0dcb2930d2c3062bca967b602583d0"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
@@ -664,9 +708,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.7"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0448174b01148032eed37ac4aed28963aaaa8cfa93569a08e5b479bbc6c2c151"
|
||||
checksum = "4b7109687aa4e177ef6fe84553af6280ef2778bdb7783ba44c9dc3399110fe64"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
@@ -674,9 +718,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.7"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18eaa56102984bed2c88ea39026cff3ce3b4c7f508ca970cedf2450ea10d4e46"
|
||||
checksum = "847ce131b72ffb13b6109a221da9ad97a64cbe48feb1028356b836b47b8f1748"
|
||||
|
||||
[[package]]
|
||||
name = "futures-cpupool"
|
||||
@@ -690,9 +734,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.7"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f5f8e0c9258abaea85e78ebdda17ef9666d390e987f006be6080dfe354b708cb"
|
||||
checksum = "4caa2b2b68b880003057c1dd49f1ed937e38f22fcf6c212188a121f08cf40a65"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
@@ -701,15 +745,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.7"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e1798854a4727ff944a7b12aa999f58ce7aa81db80d2dfaaf2ba06f065ddd2b"
|
||||
checksum = "611834ce18aaa1bd13c4b374f5d653e1027cf99b6b502584ff8c9a64413b30bb"
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.7"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e36fccf3fc58563b4a14d265027c627c3b665d7fed489427e88e7cc929559efe"
|
||||
checksum = "77408a692f1f97bcc61dc001d752e00643408fbc922e4d634c655df50d595556"
|
||||
dependencies = [
|
||||
"proc-macro-hack",
|
||||
"proc-macro2 1.0.24",
|
||||
@@ -719,24 +763,24 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.7"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e3ca3f17d6e8804ae5d3df7a7d35b2b3a6fe89dac84b31872720fc3060a0b11"
|
||||
checksum = "f878195a49cee50e006b02b93cf7e0a95a38ac7b776b4c4d9cc1207cd20fcb3d"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.7"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96d502af37186c4fef99453df03e374683f8a1eec9dcc1e66b3b82dc8278ce3c"
|
||||
checksum = "7c554eb5bf48b2426c4771ab68c6b14468b6e76cc90996f528c3338d761a4d0d"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.7"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "abcb44342f62e6f3e8ac427b8aa815f724fd705dfad060b18ac7866c15bb8e34"
|
||||
checksum = "d304cff4a7b99cfb7986f7d43fbe93d175e72e704a8860787cc95e9ffd85cbd2"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
@@ -828,7 +872,7 @@ version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e4728fd124914ad25e99e3d15a9361a879f6620f63cb56bbb08f95abb97a535"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"bytes 0.5.6",
|
||||
"fnv",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
@@ -836,7 +880,7 @@ dependencies = [
|
||||
"http",
|
||||
"indexmap",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio 0.2.22",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
"tracing-futures",
|
||||
@@ -879,7 +923,7 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28d569972648b2c512421b5f2a405ad6ac9666547189d0c5477a3f200f3e02f9"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"bytes 0.5.6",
|
||||
"fnv",
|
||||
"itoa",
|
||||
]
|
||||
@@ -890,7 +934,7 @@ version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13d5ff830006f7646652e057693569bfe0d51760c0085a071769d142a205111b"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"bytes 0.5.6",
|
||||
"http",
|
||||
]
|
||||
|
||||
@@ -921,7 +965,7 @@ version = "0.13.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f3afcfae8af5ad0576a31e768415edb627824129e8e5a29b8bfccb2f234e835"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"bytes 0.5.6",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
@@ -933,7 +977,7 @@ dependencies = [
|
||||
"itoa",
|
||||
"pin-project 0.4.27",
|
||||
"socket2",
|
||||
"tokio",
|
||||
"tokio 0.2.22",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
"want",
|
||||
@@ -945,10 +989,10 @@ version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d979acc56dcb5b8dddba3917601745e877576475aa046df3226eabdecef78eed"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"bytes 0.5.6",
|
||||
"hyper",
|
||||
"native-tls",
|
||||
"tokio",
|
||||
"tokio 0.2.22",
|
||||
"tokio-tls",
|
||||
]
|
||||
|
||||
@@ -973,6 +1017,15 @@ dependencies = [
|
||||
"hashbrown 0.9.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iovec"
|
||||
version = "0.1.4"
|
||||
@@ -1091,6 +1144,15 @@ version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312"
|
||||
dependencies = [
|
||||
"scopeguard 1.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.11"
|
||||
@@ -1164,26 +1226,16 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio-named-pipes"
|
||||
version = "0.1.7"
|
||||
name = "mio"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0840c1c50fd55e521b247f949c241c9997709f23bd7f023b9762cd561e935656"
|
||||
checksum = "f33bc887064ef1fd66020c9adfc45bb9f33d75a42096c81e7c56c65b75dd1a8b"
|
||||
dependencies = [
|
||||
"log",
|
||||
"mio",
|
||||
"miow 0.3.5",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio-uds"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0"
|
||||
dependencies = [
|
||||
"iovec",
|
||||
"libc",
|
||||
"mio",
|
||||
"log",
|
||||
"miow 0.3.6",
|
||||
"ntapi",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1200,9 +1252,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "miow"
|
||||
version = "0.3.5"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07b88fb9795d4d36d62a012dfbf49a8f5cf12751f36d31a9dbe66d528e58979e"
|
||||
checksum = "5a33c1b55807fbed163481b5ba66db4b2fa6cde694a5027be10fb724206c5897"
|
||||
dependencies = [
|
||||
"socket2",
|
||||
"winapi 0.3.9",
|
||||
@@ -1259,6 +1311,18 @@ dependencies = [
|
||||
"void",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.19.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2ccba0cfe4fdf15982d1674c69b1fd80bad427d293849982668dfe454bd61f2"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cc",
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "4.2.3"
|
||||
@@ -1281,6 +1345,15 @@ dependencies = [
|
||||
"version_check 0.9.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ntapi"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44"
|
||||
dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.13.0"
|
||||
@@ -1348,6 +1421,32 @@ dependencies = [
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb"
|
||||
dependencies = [
|
||||
"instant",
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c361aa727dd08437f2f1447be8b59a33b0edd15e0fcee698f935613d9efbca9b"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10",
|
||||
"cloudabi 0.1.0",
|
||||
"instant",
|
||||
"libc",
|
||||
"redox_syscall 0.1.57",
|
||||
"smallvec",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "peeking_take_while"
|
||||
version = "0.1.2"
|
||||
@@ -1449,6 +1548,12 @@ version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c917123afa01924fc84bb20c4c03f004d9c38e5127e3c039bbf7f4b9c76a2f6b"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b063f57ec186e6140e2b8b6921e5f1bd89c7356dda5b33acc5401203ca6131c"
|
||||
|
||||
[[package]]
|
||||
name = "pin-utils"
|
||||
version = "0.1.0"
|
||||
@@ -1684,7 +1789,7 @@ version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071"
|
||||
dependencies = [
|
||||
"cloudabi",
|
||||
"cloudabi 0.0.3",
|
||||
"fuchsia-cprng",
|
||||
"libc",
|
||||
"rand_core 0.4.2",
|
||||
@@ -1726,6 +1831,15 @@ version = "0.1.57"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48b82c2a1e8eb6e1bfde608de2bcbebd4072aa32d056ea48a986990cd5ca0f5a"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.3.5"
|
||||
@@ -1733,7 +1847,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"redox_syscall",
|
||||
"redox_syscall 0.1.57",
|
||||
"rust-argon2",
|
||||
]
|
||||
|
||||
@@ -1775,12 +1889,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.10.8"
|
||||
version = "0.10.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e9eaa17ac5d7b838b7503d118fa16ad88f440498bf9ffe5424e621f93190d61e"
|
||||
checksum = "fb15d6255c792356a0f578d8a645c677904dc02e862bebe2ecc18e0c01b9a0ce"
|
||||
dependencies = [
|
||||
"base64 0.12.3",
|
||||
"bytes",
|
||||
"base64 0.13.0",
|
||||
"bytes 0.5.6",
|
||||
"encoding_rs",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
@@ -1796,15 +1910,16 @@ dependencies = [
|
||||
"mime_guess",
|
||||
"native-tls",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"pin-project-lite 0.2.0",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"tokio",
|
||||
"tokio 0.2.22",
|
||||
"tokio-tls",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"wasm-bindgen-test",
|
||||
"web-sys",
|
||||
"winreg",
|
||||
]
|
||||
@@ -1859,6 +1974,12 @@ dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scoped-tls"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "0.3.3"
|
||||
@@ -1896,9 +2017,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.117"
|
||||
version = "1.0.118"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a"
|
||||
checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
@@ -1915,9 +2036,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.117"
|
||||
version = "1.0.118"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e"
|
||||
checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.24",
|
||||
"quote 1.0.7",
|
||||
@@ -1946,14 +2067,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_urlencoded"
|
||||
version = "0.6.1"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ec5d77e2d4c73717816afac02670d5c4f534ea95ed430442cad02e7a6e32c97"
|
||||
checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9"
|
||||
dependencies = [
|
||||
"dtoa",
|
||||
"form_urlencoded",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2026,14 +2147,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.3.15"
|
||||
name = "smallvec"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1fa70dc5c8104ec096f4fe7ede7a221d35ae13dcd19ba1ad9a81d2cab9a1c44"
|
||||
checksum = "7acad6f34eb9e8a259d3283d1e8c1d34d7415943d4895f65cc73813c7396fc85"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.3.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c29947abdee2a218277abeca306f25789c938e500ea5a9d4b12a5a504466902"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10",
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"redox_syscall 0.1.57",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
@@ -2103,7 +2230,7 @@ dependencies = [
|
||||
"cfg-if 0.1.10",
|
||||
"libc",
|
||||
"rand 0.7.3",
|
||||
"redox_syscall",
|
||||
"redox_syscall 0.1.57",
|
||||
"remove_dir_all",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
@@ -2144,7 +2271,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7fbf4c9d56b320106cd64fd024dadfa0be7cb4706725fc44a7d7ce952d820c1"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"redox_syscall 0.1.57",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
@@ -2169,18 +2296,34 @@ version = "0.2.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d34ca54d84bf2b5b4d7d31e901a8464f7b60ac145a284fba25ceb801f2ddccd"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"bytes 0.5.6",
|
||||
"fnv",
|
||||
"futures-core",
|
||||
"iovec",
|
||||
"lazy_static",
|
||||
"memchr",
|
||||
"mio 0.6.22",
|
||||
"num_cpus",
|
||||
"pin-project-lite 0.1.11",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a12a3eb39ee2c231be64487f1fcbe726c8f2514876a55480a5ab8559fc374252"
|
||||
dependencies = [
|
||||
"autocfg 1.0.1",
|
||||
"bytes 0.6.0",
|
||||
"futures-core",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"memchr",
|
||||
"mio",
|
||||
"mio-named-pipes",
|
||||
"mio-uds",
|
||||
"mio 0.7.6",
|
||||
"num_cpus",
|
||||
"pin-project-lite",
|
||||
"parking_lot",
|
||||
"pin-project-lite 0.2.0",
|
||||
"signal-hook-registry",
|
||||
"slab",
|
||||
"tokio-macros",
|
||||
@@ -2188,10 +2331,23 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "0.2.5"
|
||||
name = "tokio-compat-02"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0c3acc6aa564495a0f2e1d59fab677cd7f81a19994cfc7f3ad0e64301560389"
|
||||
checksum = "bb4cec419b8b6f06c32e74aae6d8c5e79646d038a38e5ea2b36045f2c3296e22"
|
||||
dependencies = [
|
||||
"bytes 0.5.6",
|
||||
"once_cell",
|
||||
"pin-project-lite 0.1.11",
|
||||
"tokio 0.2.22",
|
||||
"tokio 0.3.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21d30fdbb5dc2d8f91049691aa1a9d4d4ae422a21c334ce8936e5886d30c5c45"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.24",
|
||||
"quote 1.0.7",
|
||||
@@ -2200,15 +2356,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio-tar"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3a9e415c93375be93253134543229563114a2be8d46440d6d8f25b2ec62a7fb"
|
||||
version = "0.3.0"
|
||||
source = "git+https://github.com/dr-bonez/tokio-tar.git?rev=1ba710f3#1ba710f344ddb2a5b4d98bb96c905195c3cd9d43"
|
||||
dependencies = [
|
||||
"filetime",
|
||||
"futures-core",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"tokio",
|
||||
"redox_syscall 0.2.1",
|
||||
"tokio 0.3.5",
|
||||
"xattr",
|
||||
]
|
||||
|
||||
@@ -2219,7 +2374,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a70f4fcd7b3b24fb194f837560168208f669ca8cb70d0c4b862944452396343"
|
||||
dependencies = [
|
||||
"native-tls",
|
||||
"tokio",
|
||||
"tokio 0.2.22",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2228,12 +2383,12 @@ version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"bytes 0.5.6",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
"pin-project-lite 0.1.11",
|
||||
"tokio 0.2.22",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2250,7 +2405,7 @@ checksum = "b0987850db3733619253fe60e17cb59b82d37c7e6c0236bb81e4d6b87c879f27"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10",
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
"pin-project-lite 0.1.11",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
@@ -2338,10 +2493,11 @@ checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.1.1"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb"
|
||||
checksum = "5909f2b0817350449ed73e8bcd81c8c3c8d9a7a5d8acba4b27db277f1868976e"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
"matches",
|
||||
"percent-encoding",
|
||||
@@ -2471,6 +2627,30 @@ version = "0.2.68"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d649a3145108d7d3fbcde896a468d1bd636791823c9921135218ad89be08307"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-test"
|
||||
version = "0.3.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34d1cdc8b98a557f24733d50a1199c4b0635e465eecba9c45b214544da197f64"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"js-sys",
|
||||
"scoped-tls",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"wasm-bindgen-test-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-test-macro"
|
||||
version = "0.3.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8fb9c67be7439ee8ab1b7db502a49c05e51e2835b66796c705134d9b8e1a585"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.24",
|
||||
"quote 1.0.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.45"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "appmgr"
|
||||
version = "0.2.6"
|
||||
authors = ["Aiden McClelland <me@drbonez.dev>"]
|
||||
edition = "2018"
|
||||
name = "appmgr"
|
||||
version = "0.2.8"
|
||||
|
||||
[lib]
|
||||
name = "appmgrlib"
|
||||
@@ -18,32 +18,34 @@ portable = []
|
||||
production = []
|
||||
|
||||
[dependencies]
|
||||
emver = { git = "https://github.com/Start9Labs/emver-rs.git", version = "0.1.0", features = ["serde"] }
|
||||
argonautica = "0.2.0"
|
||||
async-trait = "0.1.41"
|
||||
async-trait = "0.1.42"
|
||||
base32 = "0.4.0"
|
||||
clap = "2.33"
|
||||
ed25519-dalek = "1.0.1"
|
||||
emver = { version = "0.1.0", features = ["serde"] }
|
||||
failure = "0.1.8"
|
||||
file-lock = "1.1"
|
||||
futures = "0.3.7"
|
||||
futures = "0.3.8"
|
||||
git-version = "0.3.4"
|
||||
itertools = "0.9.0"
|
||||
lazy_static = "1.4"
|
||||
linear-map = { version = "1.2", features = ["serde_impl"] }
|
||||
log = "0.4.11"
|
||||
nix = "0.19.1"
|
||||
openssl = "0.10.30"
|
||||
pest = "2.1"
|
||||
pest_derive = "2.1"
|
||||
prettytable-rs = "0.8.0"
|
||||
rand = "0.7.3"
|
||||
regex = "1.4.2"
|
||||
reqwest = { version = "0.10.8", features = ["stream", "json"] }
|
||||
reqwest = { version = "0.10.9", features = ["stream", "json"] }
|
||||
rpassword = "5.0.0"
|
||||
serde = { version = "1.0.117", features = ["derive", "rc"] }
|
||||
serde_yaml = "0.8.14"
|
||||
serde = { version = "1.0.118", features = ["derive", "rc"] }
|
||||
serde_cbor = "0.11.1"
|
||||
serde_json = "1.0.59"
|
||||
serde_yaml = "0.8.14"
|
||||
simple-logging = "2.0"
|
||||
tokio = { version = "0.2.22", features = ["full"] }
|
||||
tokio-tar = "0.2.0"
|
||||
tokio = { version = "0.3.5", features = ["full"] }
|
||||
tokio-compat-02 = "0.1.2"
|
||||
tokio-tar = { version = "0.3.0", git = "https://github.com/dr-bonez/tokio-tar.git", rev = "1ba710f3" }
|
||||
|
||||
@@ -131,7 +131,7 @@ pub async fn remove(id: &str) -> Result<(), failure::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn status(id: &str) -> Result<AppStatus, Error> {
|
||||
pub async fn status(id: &str, remap_crashed: bool) -> Result<AppStatus, Error> {
|
||||
let output = std::process::Command::new("docker")
|
||||
.args(&["inspect", id, "--format", "{{.State.Status}}"])
|
||||
.stdout(std::process::Stdio::piped())
|
||||
@@ -155,6 +155,19 @@ pub async fn status(id: &str) -> Result<AppStatus, Error> {
|
||||
"restarting" => DockerStatus::Restarting,
|
||||
"removing" => DockerStatus::Removing,
|
||||
"dead" => DockerStatus::Dead,
|
||||
"exited"
|
||||
if remap_crashed && {
|
||||
let path = PersistencePath::from_ref("running.yaml");
|
||||
if let Some(mut f) = path.maybe_read(false).await.transpose()? {
|
||||
let running: Vec<String> = from_yaml_async_reader(&mut *f).await?;
|
||||
running.iter().filter(|a| a.as_str() == id).next().is_some()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} =>
|
||||
{
|
||||
DockerStatus::Restarting
|
||||
}
|
||||
"created" | "exited" => DockerStatus::Stopped,
|
||||
"paused" => DockerStatus::Paused,
|
||||
_ => Err(format_err!("unknown status: {}", status))?,
|
||||
@@ -275,7 +288,7 @@ pub async fn info_full(
|
||||
Ok(AppInfoFull {
|
||||
info: info(id).await?,
|
||||
status: if with_status {
|
||||
Some(status(id).await?)
|
||||
Some(status(id, true).await?)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
@@ -379,8 +392,12 @@ pub async fn list(
|
||||
let info = list_info().await?;
|
||||
futures::future::join_all(info.into_iter().map(move |(id, info)| async move {
|
||||
let (status, manifest, config, dependencies) = futures::try_join!(
|
||||
OptionFuture::from(if with_status { Some(status(&id)) } else { None })
|
||||
.map(Option::transpose),
|
||||
OptionFuture::from(if with_status {
|
||||
Some(status(&id, true))
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.map(Option::transpose),
|
||||
OptionFuture::from(if with_manifest {
|
||||
Some(manifest(&id))
|
||||
} else {
|
||||
|
||||
@@ -1,15 +1,25 @@
|
||||
use std::path::Path;
|
||||
|
||||
use argonautica::{Hasher, Verifier};
|
||||
use emver::Version;
|
||||
use futures::try_join;
|
||||
use futures::TryStreamExt;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::apps;
|
||||
use crate::util::from_yaml_async_reader;
|
||||
use crate::util::to_yaml_async_writer;
|
||||
use crate::util::Invoke;
|
||||
use crate::version::VersionT;
|
||||
use crate::Error;
|
||||
use crate::ResultExt;
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct Metadata {
|
||||
pub app_version: Version,
|
||||
pub os_version: &'static Version,
|
||||
}
|
||||
|
||||
pub async fn create_backup<P: AsRef<Path>>(
|
||||
path: P,
|
||||
app_id: &str,
|
||||
@@ -21,6 +31,7 @@ pub async fn create_backup<P: AsRef<Path>>(
|
||||
crate::error::FILESYSTEM_ERROR,
|
||||
"Backup Path Must Be Directory"
|
||||
);
|
||||
let metadata_path = path.join("metadata.yaml");
|
||||
let pw_path = path.join("password");
|
||||
let data_path = path.join("data");
|
||||
let tor_path = path.join("tor");
|
||||
@@ -56,7 +67,17 @@ pub async fn create_backup<P: AsRef<Path>>(
|
||||
f.flush().await?;
|
||||
}
|
||||
|
||||
let status = crate::apps::status(app_id).await?;
|
||||
let info = crate::apps::info(app_id).await?;
|
||||
to_yaml_async_writer(
|
||||
tokio::fs::File::create(metadata_path).await?,
|
||||
&Metadata {
|
||||
app_version: info.version,
|
||||
os_version: crate::version::Current::new().semver(),
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
let status = crate::apps::status(app_id, false).await?;
|
||||
let exclude = if volume_path.is_dir() {
|
||||
let ignore_path = volume_path.join(".backupignore");
|
||||
if ignore_path.is_file() {
|
||||
@@ -124,6 +145,7 @@ pub async fn restore_backup<P: AsRef<Path>>(
|
||||
crate::error::FILESYSTEM_ERROR,
|
||||
"Backup Path Must Be Directory"
|
||||
);
|
||||
let metadata_path = path.join("metadata.yaml");
|
||||
let pw_path = path.join("password");
|
||||
let data_path = path.join("data");
|
||||
let tor_path = path.join("tor");
|
||||
@@ -148,7 +170,7 @@ pub async fn restore_backup<P: AsRef<Path>>(
|
||||
);
|
||||
}
|
||||
|
||||
let status = crate::apps::status(app_id).await?;
|
||||
let status = crate::apps::status(app_id, false).await?;
|
||||
let running = status.status == crate::apps::DockerStatus::Running;
|
||||
if running {
|
||||
crate::control::stop_app(app_id, true, false).await?;
|
||||
@@ -181,12 +203,21 @@ pub async fn restore_backup<P: AsRef<Path>>(
|
||||
);
|
||||
|
||||
// Fix the tor address in apps.yaml
|
||||
let mut yhdl = apps::list_info_mut().await?;
|
||||
let mut yhdl = crate::apps::list_info_mut().await?;
|
||||
if let Some(app_info) = yhdl.get_mut(app_id) {
|
||||
app_info.tor_address = Some(crate::tor::read_tor_address(app_id, None).await?);
|
||||
}
|
||||
yhdl.commit().await?;
|
||||
|
||||
tokio::fs::copy(
|
||||
metadata_path,
|
||||
Path::new(crate::VOLUMES)
|
||||
.join(app_id)
|
||||
.join("start9")
|
||||
.join("restore.yaml"),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Attempt to configure the service with the config coming from restoration
|
||||
let cfg_path = Path::new(crate::VOLUMES)
|
||||
.join(app_id)
|
||||
|
||||
@@ -137,7 +137,9 @@ pub async fn configure(
|
||||
&mut res.stopped,
|
||||
)
|
||||
.await?;
|
||||
if crate::apps::status(&dependent).await?.status != crate::apps::DockerStatus::Stopped {
|
||||
if crate::apps::status(&dependent, false).await?.status
|
||||
!= crate::apps::DockerStatus::Stopped
|
||||
{
|
||||
crate::control::stop_app(&dependent, false, dry_run).await?;
|
||||
res.stopped.insert(
|
||||
// TODO: maybe don't do this if its not running
|
||||
@@ -283,7 +285,8 @@ pub async fn configure(
|
||||
crate::apps::set_configured(name, true).await?;
|
||||
crate::apps::set_recoverable(name, false).await?;
|
||||
}
|
||||
if crate::apps::status(name).await?.status != crate::apps::DockerStatus::Stopped {
|
||||
if crate::apps::status(name, false).await?.status != crate::apps::DockerStatus::Stopped
|
||||
{
|
||||
if !dry_run {
|
||||
crate::apps::set_needs_restart(name, true).await?;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use std::path::Path;
|
||||
|
||||
use futures::future::{BoxFuture, FutureExt};
|
||||
use linear_map::LinearMap;
|
||||
use linear_map::{set::LinearSet, LinearMap};
|
||||
|
||||
use crate::dependencies::{DependencyError, TaggedDependencyError};
|
||||
use crate::util::{from_yaml_async_reader, PersistencePath, YamlUpdateHandle};
|
||||
use crate::Error;
|
||||
|
||||
pub async fn start_app(name: &str, update_metadata: bool) -> Result<(), Error> {
|
||||
@@ -19,7 +20,7 @@ pub async fn start_app(name: &str, update_metadata: bool) -> Result<(), Error> {
|
||||
true,
|
||||
)
|
||||
.await?;
|
||||
let status = crate::apps::status(name).await?.status;
|
||||
let status = crate::apps::status(name, false).await?.status;
|
||||
if status == crate::apps::DockerStatus::Stopped {
|
||||
if update_metadata {
|
||||
crate::config::configure(name, None, None, false).await?;
|
||||
@@ -27,6 +28,10 @@ pub async fn start_app(name: &str, update_metadata: bool) -> Result<(), Error> {
|
||||
crate::dependencies::update_binds(name).await?;
|
||||
}
|
||||
crate::apps::set_needs_restart(name, false).await?;
|
||||
let mut running = YamlUpdateHandle::<LinearSet<String>>::new_or_default(
|
||||
PersistencePath::from_ref("running.yaml"),
|
||||
)
|
||||
.await?;
|
||||
let output = tokio::process::Command::new("docker")
|
||||
.args(&["start", name])
|
||||
.stdout(std::process::Stdio::null())
|
||||
@@ -38,6 +43,8 @@ pub async fn start_app(name: &str, update_metadata: bool) -> Result<(), Error> {
|
||||
"Failed to Start Application: {}",
|
||||
std::str::from_utf8(&output.stderr).unwrap_or("Unknown Error")
|
||||
);
|
||||
running.insert(name.to_owned());
|
||||
running.commit().await?;
|
||||
} else if status == crate::apps::DockerStatus::Paused {
|
||||
resume_app(name).await?;
|
||||
}
|
||||
@@ -67,6 +74,10 @@ pub async fn stop_app(
|
||||
true,
|
||||
)
|
||||
.await?;
|
||||
let mut running = YamlUpdateHandle::<LinearSet<String>>::new_or_default(
|
||||
PersistencePath::from_ref("running.yaml"),
|
||||
)
|
||||
.await?;
|
||||
log::info!("Stopping {}", name);
|
||||
let output = tokio::process::Command::new("docker")
|
||||
.args(&["stop", "-t", "25", name])
|
||||
@@ -79,6 +90,8 @@ pub async fn stop_app(
|
||||
"Failed to Stop Application: {}",
|
||||
std::str::from_utf8(&output.stderr).unwrap_or("Unknown Error")
|
||||
);
|
||||
running.remove(name);
|
||||
running.commit().await?;
|
||||
crate::util::unlock(lock).await?;
|
||||
}
|
||||
Ok(res)
|
||||
@@ -98,7 +111,7 @@ pub async fn stop_dependents(
|
||||
) -> BoxFuture<'a, Result<(), Error>> {
|
||||
async move {
|
||||
for dependent in crate::apps::dependents(name, false).await? {
|
||||
if crate::apps::status(&dependent).await?.status
|
||||
if crate::apps::status(&dependent, false).await?.status
|
||||
!= crate::apps::DockerStatus::Stopped
|
||||
{
|
||||
stop_dependents_rec(&dependent, dry_run, DependencyError::NotRunning, res)
|
||||
@@ -192,3 +205,34 @@ pub async fn resume_app(name: &str) -> Result<(), Error> {
|
||||
crate::util::unlock(lock).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn repair_app_status() -> Result<(), Error> {
|
||||
let mut running_file = PersistencePath::from_ref("running.yaml")
|
||||
.maybe_read(false)
|
||||
.await
|
||||
.transpose()?;
|
||||
let running: Vec<String> = if let Some(f) = running_file.as_mut() {
|
||||
from_yaml_async_reader::<_, &mut tokio::fs::File>(f).await?
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
for name in running {
|
||||
let lock = crate::util::lock_file(
|
||||
format!(
|
||||
"{}",
|
||||
Path::new(crate::PERSISTENCE_DIR)
|
||||
.join("apps")
|
||||
.join(&name)
|
||||
.join("control.lock")
|
||||
.display()
|
||||
),
|
||||
true,
|
||||
)
|
||||
.await?;
|
||||
if crate::apps::status(&name, false).await?.status == crate::apps::DockerStatus::Stopped {
|
||||
start_app(&name, true).await?;
|
||||
}
|
||||
crate::util::unlock(lock).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -115,7 +115,9 @@ impl DepInfo {
|
||||
if !errors.is_empty() {
|
||||
return Ok(Err(DependencyError::ConfigUnsatisfied(errors)));
|
||||
}
|
||||
if crate::apps::status(dependency_id).await?.status != crate::apps::DockerStatus::Running {
|
||||
if crate::apps::status(dependency_id, false).await?.status
|
||||
!= crate::apps::DockerStatus::Running
|
||||
{
|
||||
return Ok(Err(DependencyError::NotRunning));
|
||||
}
|
||||
Ok(Ok(()))
|
||||
|
||||
@@ -30,6 +30,7 @@ pub struct VersionInfo {
|
||||
pub release_notes: String,
|
||||
pub os_version_required: VersionRange,
|
||||
pub os_version_recommended: VersionRange,
|
||||
pub install_alert: Option<String>,
|
||||
}
|
||||
|
||||
const NULL_VERSION: Version = Version::new(0, 0, 0, 0);
|
||||
@@ -52,6 +53,7 @@ impl AppIndex {
|
||||
release_notes: manifest.release_notes,
|
||||
os_version_required: manifest.os_version_required,
|
||||
os_version_recommended: manifest.os_version_recommended,
|
||||
install_alert: manifest.install_alert,
|
||||
});
|
||||
entry
|
||||
.version_info
|
||||
@@ -68,6 +70,7 @@ impl AppIndex {
|
||||
release_notes: manifest.release_notes,
|
||||
os_version_required: manifest.os_version_required,
|
||||
os_version_recommended: manifest.os_version_recommended,
|
||||
install_alert: manifest.install_alert,
|
||||
}],
|
||||
icon_type: "png".to_owned(), // TODO
|
||||
},
|
||||
|
||||
@@ -14,8 +14,9 @@ use std::time::Duration;
|
||||
use failure::ResultExt as _;
|
||||
use futures::stream::StreamExt;
|
||||
use futures::stream::TryStreamExt;
|
||||
use tokio::io::AsyncRead;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::io::{AsyncRead, ReadBuf};
|
||||
use tokio_compat_02::FutureExt;
|
||||
use tokio_tar as tar;
|
||||
|
||||
use crate::config::{ConfigRuleEntry, ConfigSpec};
|
||||
@@ -62,13 +63,13 @@ where
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut [u8],
|
||||
) -> Poll<std::io::Result<usize>> {
|
||||
buf: &mut ReadBuf,
|
||||
) -> Poll<std::io::Result<()>> {
|
||||
let atomic = self.as_ref().1.clone(); // TODO: not efficient
|
||||
match unsafe { self.map_unchecked_mut(|a| &mut a.0) }.poll_read(cx, buf) {
|
||||
Poll::Ready(Ok(res)) => {
|
||||
atomic.fetch_add(res as u64, atomic::Ordering::SeqCst);
|
||||
Poll::Ready(Ok(res))
|
||||
Poll::Ready(Ok(())) => {
|
||||
atomic.fetch_add(buf.filled().len() as u64, atomic::Ordering::SeqCst);
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
a => a,
|
||||
}
|
||||
@@ -98,6 +99,7 @@ pub async fn download(url: &str, name: Option<&str>) -> Result<PathBuf, crate::E
|
||||
let url = reqwest::Url::parse(url).no_code()?;
|
||||
log::info!("Downloading {}.", url.as_str());
|
||||
let response = reqwest::get(url)
|
||||
.compat()
|
||||
.await
|
||||
.with_code(crate::error::NETWORK_ERROR)?
|
||||
.error_for_status()
|
||||
@@ -141,7 +143,7 @@ pub async fn download(url: &str, name: Option<&str>) -> Result<PathBuf, crate::E
|
||||
if is_done {
|
||||
break;
|
||||
}
|
||||
tokio::time::delay_for(Duration::from_millis(10)).await;
|
||||
tokio::time::sleep(Duration::from_millis(10)).await;
|
||||
}
|
||||
if !*crate::QUIET.read().await {
|
||||
println!("\rDownloading... 100%");
|
||||
@@ -191,7 +193,7 @@ pub async fn install_path<P: AsRef<Path>>(p: P, name: Option<&str>) -> Result<()
|
||||
if is_done {
|
||||
break;
|
||||
}
|
||||
tokio::time::delay_for(Duration::from_millis(10)).await;
|
||||
tokio::time::sleep(Duration::from_millis(10)).await;
|
||||
}
|
||||
if !*crate::QUIET.read().await {
|
||||
println!("\rInstalling... 100%");
|
||||
@@ -407,11 +409,13 @@ pub async fn install_v0<R: AsyncRead + Unpin + Send + Sync>(
|
||||
.arg("stop")
|
||||
.arg(&manifest.id)
|
||||
.spawn()?
|
||||
.wait()
|
||||
.await?;
|
||||
tokio::process::Command::new("docker")
|
||||
.arg("rm")
|
||||
.arg(&manifest.id)
|
||||
.spawn()?
|
||||
.wait()
|
||||
.await?;
|
||||
crate::ensure_code!(
|
||||
tokio::process::Command::new("docker")
|
||||
@@ -457,7 +461,7 @@ pub async fn install_v0<R: AsyncRead + Unpin + Send + Sync>(
|
||||
child_in.shutdown().await?;
|
||||
drop(child_in);
|
||||
crate::ensure_code!(
|
||||
child.await?.success(),
|
||||
child.wait().await?.success(),
|
||||
crate::error::DOCKER_ERROR,
|
||||
"Failed to Load Docker Image From Tar"
|
||||
);
|
||||
@@ -474,7 +478,7 @@ pub async fn install_v0<R: AsyncRead + Unpin + Send + Sync>(
|
||||
let mut args = vec![
|
||||
Cow::Borrowed(OsStr::new("create")),
|
||||
Cow::Borrowed(OsStr::new("--restart")),
|
||||
Cow::Borrowed(OsStr::new("on-failure")),
|
||||
Cow::Borrowed(OsStr::new("no")),
|
||||
Cow::Borrowed(OsStr::new("--name")),
|
||||
Cow::Borrowed(OsStr::new(&manifest.id)),
|
||||
Cow::Borrowed(OsStr::new("--mount")),
|
||||
@@ -554,7 +558,8 @@ pub async fn install_v0<R: AsyncRead + Unpin + Send + Sync>(
|
||||
if dep_info.mount_shared
|
||||
&& crate::apps::list_info().await?.get(&dep_id).is_some()
|
||||
&& crate::apps::manifest(&dep_id).await?.shared.is_some()
|
||||
&& crate::apps::status(&dep_id).await?.status != crate::apps::DockerStatus::Stopped
|
||||
&& crate::apps::status(&dep_id, false).await?.status
|
||||
!= crate::apps::DockerStatus::Stopped
|
||||
{
|
||||
crate::apps::set_needs_restart(&dep_id, true).await?;
|
||||
}
|
||||
|
||||
@@ -817,6 +817,9 @@ async fn inner_main() -> Result<(), Error> {
|
||||
.help("Password to use for encryption of backup file"),
|
||||
),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("repair-app-status").about("Restarts crashed apps"), // TODO: remove
|
||||
);
|
||||
|
||||
let matches = app.clone().get_matches();
|
||||
@@ -1540,6 +1543,10 @@ async fn inner_main() -> Result<(), Error> {
|
||||
std::process::exit(1);
|
||||
}
|
||||
},
|
||||
#[cfg(not(feature = "portable"))]
|
||||
("repair-app-status", _) => {
|
||||
control::repair_app_status().await?;
|
||||
}
|
||||
("pack", Some(sub_m)) => {
|
||||
pack(
|
||||
sub_m.value_of("PATH").unwrap(),
|
||||
|
||||
@@ -37,6 +37,12 @@ pub struct ManifestV0 {
|
||||
pub description: Description,
|
||||
pub release_notes: String,
|
||||
#[serde(default)]
|
||||
pub install_alert: Option<String>,
|
||||
#[serde(default)]
|
||||
pub uninstall_alert: Option<String>,
|
||||
#[serde(default)]
|
||||
pub restore_alert: Option<String>,
|
||||
#[serde(default)]
|
||||
pub has_instructions: bool,
|
||||
#[serde(default = "emver::VersionRange::any")]
|
||||
pub os_version_required: emver::VersionRange,
|
||||
|
||||
@@ -25,14 +25,6 @@ pub enum Error {
|
||||
pub async fn pack(path: &str, output: &str) -> Result<(), failure::Error> {
|
||||
let path = Path::new(path.trim_end_matches("/"));
|
||||
let output = Path::new(output);
|
||||
ensure!(
|
||||
output
|
||||
.extension()
|
||||
.and_then(|a| a.to_str())
|
||||
.ok_or_else(|| Error::InvalidOutputPath(format!("{}", output.display())))?
|
||||
== "s9pk",
|
||||
"Extension Must Be '.s9pk'"
|
||||
);
|
||||
log::info!(
|
||||
"Starting pack of {} to {}.",
|
||||
path.file_name()
|
||||
@@ -74,9 +66,6 @@ pub async fn pack(path: &str, output: &str) -> Result<(), failure::Error> {
|
||||
.with_context(|e| format!("{}: config_spec.yaml", e))?,
|
||||
)
|
||||
.await?;
|
||||
config_spec.validate(&manifest)?;
|
||||
let config = config_spec.gen(&mut rand::rngs::StdRng::from_entropy(), &None)?;
|
||||
config_spec.matches(&config)?;
|
||||
log::info!("Writing config spec to archive.");
|
||||
let bin_config_spec = serde_cbor::to_vec(&config_spec)?;
|
||||
let mut config_spec_header = tar::Header::new_gnu();
|
||||
@@ -94,12 +83,6 @@ pub async fn pack(path: &str, output: &str) -> Result<(), failure::Error> {
|
||||
.with_context(|e| format!("{}: config_rules.yaml", e))?,
|
||||
)
|
||||
.await?;
|
||||
let mut cfgs = LinearMap::new();
|
||||
cfgs.insert(manifest.id.as_str(), Cow::Borrowed(&config));
|
||||
for rule in &config_rules {
|
||||
rule.check(&config, &cfgs)
|
||||
.with_context(|e| format!("Default Config does not satisfy: {}", e))?;
|
||||
}
|
||||
log::info!("Writing config rules to archive.");
|
||||
let bin_config_rules = serde_cbor::to_vec(&config_rules)?;
|
||||
let mut config_rules_header = tar::Header::new_gnu();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use emver::VersionRange;
|
||||
use tokio_compat_02::FutureExt;
|
||||
|
||||
use crate::apps::AppConfig;
|
||||
use crate::manifest::ManifestLatest;
|
||||
@@ -12,6 +13,7 @@ pub async fn manifest(id: &str, version: &VersionRange) -> Result<ManifestLatest
|
||||
id,
|
||||
version
|
||||
))
|
||||
.compat()
|
||||
.await
|
||||
.with_code(crate::error::NETWORK_ERROR)?
|
||||
.error_for_status()
|
||||
@@ -34,6 +36,7 @@ pub async fn version(id: &str, version: &VersionRange) -> Result<emver::Version,
|
||||
id,
|
||||
version
|
||||
))
|
||||
.compat()
|
||||
.await
|
||||
.with_code(crate::error::NETWORK_ERROR)?
|
||||
.error_for_status()
|
||||
@@ -51,6 +54,7 @@ pub async fn config(id: &str, version: &VersionRange) -> Result<AppConfig, Error
|
||||
id,
|
||||
version
|
||||
))
|
||||
.compat()
|
||||
.await
|
||||
.with_code(crate::error::NETWORK_ERROR)?
|
||||
.error_for_status()
|
||||
|
||||
@@ -198,7 +198,7 @@ pub async fn read_tor_address(name: &str, timeout: Option<Duration>) -> Result<S
|
||||
}
|
||||
}
|
||||
} {
|
||||
tokio::time::delay_for(Duration::from_millis(100)).await;
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
}
|
||||
}
|
||||
let tor_addr = match tokio::fs::read_to_string(&addr_path).await {
|
||||
@@ -217,7 +217,7 @@ pub async fn read_tor_key(
|
||||
version: HiddenServiceVersion,
|
||||
timeout: Option<Duration>,
|
||||
) -> Result<String, Error> {
|
||||
log::info!("Retrieving Tor hidden service address for {}.", name);
|
||||
log::info!("Retrieving Tor hidden service key for {}.", name);
|
||||
let addr_path = Path::new(HIDDEN_SERVICE_DIR_ROOT)
|
||||
.join(format!("app-{}", name))
|
||||
.join(match version {
|
||||
@@ -238,7 +238,7 @@ pub async fn read_tor_key(
|
||||
}
|
||||
}
|
||||
} {
|
||||
tokio::time::delay_for(Duration::from_millis(100)).await;
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
}
|
||||
}
|
||||
let tor_key = match version {
|
||||
@@ -277,6 +277,17 @@ pub async fn set_svc(
|
||||
let ip = hidden_services.add(name.to_owned(), service);
|
||||
log::info!("Adding Tor hidden service {} to {}.", name, ETC_TOR_RC);
|
||||
write_services(&hidden_services).await?;
|
||||
let addr_path = Path::new(HIDDEN_SERVICE_DIR_ROOT)
|
||||
.join(format!("app-{}", name))
|
||||
.join("hostname");
|
||||
tokio::fs::remove_file(addr_path).await.or_else(|e| {
|
||||
if e.kind() == std::io::ErrorKind::NotFound {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(e)
|
||||
}
|
||||
})?;
|
||||
nix::unistd::sync();
|
||||
hidden_services.commit().await?;
|
||||
log::info!("Reloading Tor.");
|
||||
let svc_exit = std::process::Command::new("service")
|
||||
|
||||
@@ -19,7 +19,9 @@ pub async fn update(
|
||||
let version = crate::registry::version(name, &version_req).await?;
|
||||
let mut res = LinearMap::new();
|
||||
for dependent in crate::apps::dependents(name, false).await? {
|
||||
if crate::apps::status(&dependent).await?.status != crate::apps::DockerStatus::Stopped {
|
||||
if crate::apps::status(&dependent, false).await?.status
|
||||
!= crate::apps::DockerStatus::Stopped
|
||||
{
|
||||
let manifest = crate::apps::manifest(&dependent).await?;
|
||||
match manifest.dependencies.0.get(name) {
|
||||
Some(dep) if !version.satisfies(&dep.version) => {
|
||||
@@ -30,7 +32,8 @@ pub async fn update(
|
||||
&mut res,
|
||||
)
|
||||
.await?;
|
||||
if crate::apps::status(name).await?.status != crate::apps::DockerStatus::Stopped
|
||||
if crate::apps::status(name, false).await?.status
|
||||
!= crate::apps::DockerStatus::Stopped
|
||||
{
|
||||
crate::control::stop_app(&dependent, false, dry_run).await?;
|
||||
res.insert(
|
||||
@@ -53,7 +56,8 @@ pub async fn update(
|
||||
&mut res,
|
||||
)
|
||||
.await?;
|
||||
if crate::apps::status(name).await?.status != crate::apps::DockerStatus::Stopped
|
||||
if crate::apps::status(name, false).await?.status
|
||||
!= crate::apps::DockerStatus::Stopped
|
||||
{
|
||||
crate::control::stop_app(&dependent, false, dry_run).await?;
|
||||
res.insert(
|
||||
|
||||
@@ -5,7 +5,7 @@ use std::path::{Path, PathBuf};
|
||||
use failure::ResultExt as _;
|
||||
use file_lock::FileLock;
|
||||
use tokio::fs::File;
|
||||
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
|
||||
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, ReadBuf};
|
||||
|
||||
use crate::Error;
|
||||
use crate::ResultExt as _;
|
||||
@@ -244,8 +244,8 @@ impl tokio::io::AsyncRead for UpdateHandle<ForRead> {
|
||||
fn poll_read(
|
||||
self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
buf: &mut [u8],
|
||||
) -> std::task::Poll<std::io::Result<usize>> {
|
||||
buf: &mut ReadBuf,
|
||||
) -> std::task::Poll<std::io::Result<()>> {
|
||||
unsafe { self.map_unchecked_mut(|a| a.file.file.as_mut().unwrap()) }.poll_read(cx, buf)
|
||||
}
|
||||
}
|
||||
@@ -371,7 +371,13 @@ where
|
||||
cx: &mut std::task::Context<'_>,
|
||||
buf: &mut [u8],
|
||||
) -> std::task::Poll<std::io::Result<usize>> {
|
||||
tokio::io::AsyncRead::poll_read(unsafe { self.map_unchecked_mut(|a| &mut a.0) }, cx, buf)
|
||||
let mut read_buf = ReadBuf::new(buf);
|
||||
tokio::io::AsyncRead::poll_read(
|
||||
unsafe { self.map_unchecked_mut(|a| &mut a.0) },
|
||||
cx,
|
||||
&mut read_buf,
|
||||
)
|
||||
.map(|res| res.map(|_| read_buf.filled().len()))
|
||||
}
|
||||
}
|
||||
impl<T> tokio::io::AsyncRead for AsyncCompat<T>
|
||||
@@ -381,9 +387,14 @@ where
|
||||
fn poll_read(
|
||||
self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
buf: &mut [u8],
|
||||
) -> std::task::Poll<std::io::Result<usize>> {
|
||||
futures::io::AsyncRead::poll_read(unsafe { self.map_unchecked_mut(|a| &mut a.0) }, cx, buf)
|
||||
buf: &mut ReadBuf,
|
||||
) -> std::task::Poll<std::io::Result<()>> {
|
||||
futures::io::AsyncRead::poll_read(
|
||||
unsafe { self.map_unchecked_mut(|a| &mut a.0) },
|
||||
cx,
|
||||
buf.initialize_unfilled(),
|
||||
)
|
||||
.map(|res| res.map(|len| buf.set_filled(len)))
|
||||
}
|
||||
}
|
||||
impl<T> futures::io::AsyncWrite for AsyncCompat<T>
|
||||
|
||||
@@ -3,6 +3,7 @@ use std::cmp::Ordering;
|
||||
use async_trait::async_trait;
|
||||
use failure::ResultExt as _;
|
||||
use futures::stream::TryStreamExt;
|
||||
use tokio_compat_02::FutureExt;
|
||||
|
||||
use crate::util::{to_yaml_async_writer, AsyncCompat, PersistencePath};
|
||||
use crate::Error;
|
||||
@@ -21,8 +22,10 @@ mod v0_2_3;
|
||||
mod v0_2_4;
|
||||
mod v0_2_5;
|
||||
mod v0_2_6;
|
||||
mod v0_2_7;
|
||||
mod v0_2_8;
|
||||
|
||||
pub use v0_2_6::Version as Current;
|
||||
pub use v0_2_8::Version as Current;
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[serde(untagged)]
|
||||
@@ -41,6 +44,8 @@ enum Version {
|
||||
V0_2_4(Wrapper<v0_2_4::Version>),
|
||||
V0_2_5(Wrapper<v0_2_5::Version>),
|
||||
V0_2_6(Wrapper<v0_2_6::Version>),
|
||||
V0_2_7(Wrapper<v0_2_7::Version>),
|
||||
V0_2_8(Wrapper<v0_2_8::Version>),
|
||||
Other(emver::Version),
|
||||
}
|
||||
|
||||
@@ -149,6 +154,8 @@ pub async fn init() -> Result<(), failure::Error> {
|
||||
Version::V0_2_4(v) => v.0.migrate_to(&Current::new()).await?,
|
||||
Version::V0_2_5(v) => v.0.migrate_to(&Current::new()).await?,
|
||||
Version::V0_2_6(v) => v.0.migrate_to(&Current::new()).await?,
|
||||
Version::V0_2_7(v) => v.0.migrate_to(&Current::new()).await?,
|
||||
Version::V0_2_8(v) => v.0.migrate_to(&Current::new()).await?,
|
||||
Version::Other(_) => (),
|
||||
// TODO find some way to automate this?
|
||||
}
|
||||
@@ -165,7 +172,7 @@ pub async fn self_update(requirement: emver::VersionRange) -> Result<(), Error>
|
||||
.collect();
|
||||
let url = format!("{}/appmgr?spec={}", &*crate::SYS_REGISTRY_URL, req_str);
|
||||
log::info!("Fetching new version from {}", url);
|
||||
let response = reqwest::get(&url)
|
||||
let response = reqwest::get(&url).compat()
|
||||
.await
|
||||
.with_code(crate::error::NETWORK_ERROR)?
|
||||
.error_for_status()
|
||||
@@ -235,6 +242,8 @@ pub async fn self_update(requirement: emver::VersionRange) -> Result<(), Error>
|
||||
Version::V0_2_4(v) => Current::new().migrate_to(&v.0).await?,
|
||||
Version::V0_2_5(v) => Current::new().migrate_to(&v.0).await?,
|
||||
Version::V0_2_6(v) => Current::new().migrate_to(&v.0).await?,
|
||||
Version::V0_2_7(v) => Current::new().migrate_to(&v.0).await?,
|
||||
Version::V0_2_8(v) => Current::new().migrate_to(&v.0).await?,
|
||||
Version::Other(_) => (),
|
||||
// TODO find some way to automate this?
|
||||
};
|
||||
|
||||
@@ -25,6 +25,7 @@ impl VersionT for Version {
|
||||
tokio::io::copy(
|
||||
&mut AsyncCompat(
|
||||
reqwest::get(&format!("{}/torrc?spec==0.0.0", &*crate::SYS_REGISTRY_URL))
|
||||
.compat()
|
||||
.await
|
||||
.with_context(|e| format!("GET {}/torrc: {}", &*crate::SYS_REGISTRY_URL, e))
|
||||
.with_code(crate::error::NETWORK_ERROR)?
|
||||
|
||||
@@ -22,6 +22,7 @@ impl VersionT for Version {
|
||||
tokio::io::copy(
|
||||
&mut AsyncCompat(
|
||||
reqwest::get(&format!("{}/torrc?spec==0.1.1", &*crate::SYS_REGISTRY_URL))
|
||||
.compat()
|
||||
.await
|
||||
.with_context(|e| format!("GET {}/torrc: {}", &*crate::SYS_REGISTRY_URL, e))
|
||||
.with_code(crate::error::NETWORK_ERROR)?
|
||||
@@ -76,6 +77,7 @@ impl VersionT for Version {
|
||||
tokio::io::copy(
|
||||
&mut AsyncCompat(
|
||||
reqwest::get(&format!("{}/torrc?spec==0.1.0", &*crate::SYS_REGISTRY_URL))
|
||||
.compat()
|
||||
.await
|
||||
.with_context(|e| format!("GET {}/torrc: {}", &*crate::SYS_REGISTRY_URL, e))
|
||||
.no_code()?
|
||||
|
||||
36
appmgr/src/version/v0_2_7.rs
Normal file
36
appmgr/src/version/v0_2_7.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
use super::*;
|
||||
use crate::util::Invoke;
|
||||
|
||||
const V0_2_7: emver::Version = emver::Version::new(0, 2, 7, 0);
|
||||
|
||||
pub struct Version;
|
||||
#[async_trait]
|
||||
impl VersionT for Version {
|
||||
type Previous = v0_2_6::Version;
|
||||
fn new() -> Self {
|
||||
Version
|
||||
}
|
||||
fn semver(&self) -> &'static emver::Version {
|
||||
&V0_2_7
|
||||
}
|
||||
async fn up(&self) -> Result<(), Error> {
|
||||
for (app_id, _) in crate::apps::list_info().await? {
|
||||
tokio::process::Command::new("docker")
|
||||
.arg("stop")
|
||||
.arg(&app_id)
|
||||
.invoke("Docker")
|
||||
.await?;
|
||||
tokio::process::Command::new("docker")
|
||||
.arg("update")
|
||||
.arg("--restart")
|
||||
.arg("no")
|
||||
.arg(&app_id)
|
||||
.invoke("Docker")
|
||||
.await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
async fn down(&self) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
36
appmgr/src/version/v0_2_8.rs
Normal file
36
appmgr/src/version/v0_2_8.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
use super::*;
|
||||
use crate::util::Invoke;
|
||||
|
||||
const V0_2_8: emver::Version = emver::Version::new(0, 2, 8, 0);
|
||||
|
||||
pub struct Version;
|
||||
#[async_trait]
|
||||
impl VersionT for Version {
|
||||
type Previous = v0_2_7::Version;
|
||||
fn new() -> Self {
|
||||
Version
|
||||
}
|
||||
fn semver(&self) -> &'static emver::Version {
|
||||
&V0_2_8
|
||||
}
|
||||
async fn up(&self) -> Result<(), Error> {
|
||||
for (app_id, _) in crate::apps::list_info().await? {
|
||||
tokio::process::Command::new("docker")
|
||||
.arg("stop")
|
||||
.arg(&app_id)
|
||||
.invoke("Docker")
|
||||
.await?;
|
||||
tokio::process::Command::new("docker")
|
||||
.arg("update")
|
||||
.arg("--restart")
|
||||
.arg("no")
|
||||
.arg(&app_id)
|
||||
.invoke("Docker")
|
||||
.await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
async fn down(&self) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
30
appmgr/taplo.toml
Normal file
30
appmgr/taplo.toml
Normal file
@@ -0,0 +1,30 @@
|
||||
include = ["Cargo.toml"]
|
||||
|
||||
[formatting]
|
||||
# Align consecutive entries vertically.
|
||||
align_entries = false
|
||||
# Append trailing commas for multi-line arrays.
|
||||
array_trailing_comma = true
|
||||
# Expand arrays to multiple lines that exceed the maximum column width.
|
||||
array_auto_expand = true
|
||||
# Collapse arrays that don't exceed the maximum column width and don't contain comments.
|
||||
array_auto_collapse = true
|
||||
# Omit white space padding from single-line arrays
|
||||
compact_arrays = true
|
||||
# Omit white space padding from the start and end of inline tables.
|
||||
compact_inline_tables = false
|
||||
# Maximum column width in characters, affects array expansion and collapse, this doesn't take whitespace into account.
|
||||
# Note that this is not set in stone, and works on a best-effort basis.
|
||||
column_width = 80
|
||||
# Indent based on tables and arrays of tables and their subtables, subtables out of order are not indented.
|
||||
indent_tables = false
|
||||
# The substring that is used for indentation, should be tabs or spaces (but technically can be anything).
|
||||
indent_string = ' '
|
||||
# Add trailing newline at the end of the file if not present.
|
||||
trailing_newline = true
|
||||
# Alphabetically reorder keys that are not separated by empty lines.
|
||||
reorder_keys = true
|
||||
# Maximum amount of allowed consecutive blank lines. This does not affect the whitespace at the end of the document, as it is always stripped.
|
||||
allowed_blank_lines = 2
|
||||
# Use CRLF for line endings.
|
||||
crlf = false
|
||||
3
docker-daemon.json
Normal file
3
docker-daemon.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"log-driver": "journald"
|
||||
}
|
||||
2
lifeline/.gitignore
vendored
Normal file
2
lifeline/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/target
|
||||
**/*.rs.bk
|
||||
6
lifeline/Cargo.lock
generated
Normal file
6
lifeline/Cargo.lock
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "lifeline"
|
||||
version = "0.1.0"
|
||||
|
||||
9
lifeline/Cargo.toml
Normal file
9
lifeline/Cargo.toml
Normal file
@@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "lifeline"
|
||||
version = "0.1.0"
|
||||
authors = ["Keagan McClelland <keagan.mcclelland@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
10
lifeline/lifeline.service
Normal file
10
lifeline/lifeline.service
Normal file
@@ -0,0 +1,10 @@
|
||||
[Unit]
|
||||
Description=Boot process for system reset.
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/bin/lifeline
|
||||
RemainAfterExit=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
1
lifeline/rustfmt.toml
Normal file
1
lifeline/rustfmt.toml
Normal file
@@ -0,0 +1 @@
|
||||
max_width = 120
|
||||
14
lifeline/src/main.rs
Normal file
14
lifeline/src/main.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
use std::time::Duration;
|
||||
|
||||
mod sound;
|
||||
|
||||
use sound::{notes::*, Song};
|
||||
|
||||
const SUCCESS_SONG: Song = Song(&[(Some(A_4), Duration::from_millis(100))]);
|
||||
|
||||
fn main() {
|
||||
std::fs::write("/sys/class/pwm/pwmchip0/export", "0").unwrap();
|
||||
let res = SUCCESS_SONG.play();
|
||||
std::fs::write("/sys/class/pwm/pwmchip0/unexport", "0").unwrap();
|
||||
res.unwrap();
|
||||
}
|
||||
93
lifeline/src/sound.rs
Normal file
93
lifeline/src/sound.rs
Normal file
@@ -0,0 +1,93 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::io::Error;
|
||||
use std::time::Duration;
|
||||
|
||||
pub mod notes {
|
||||
pub const A_4: f64 = 440.0;
|
||||
pub const B_4: f64 = 493.88;
|
||||
pub const C_5: f64 = 523.25;
|
||||
pub const D_5: f64 = 587.33;
|
||||
pub const E_5: f64 = 659.25;
|
||||
pub const F_5: f64 = 698.46;
|
||||
pub const G_5: f64 = 783.99;
|
||||
pub const A_5: f64 = 880.00;
|
||||
pub const B_5: f64 = 987.77;
|
||||
pub const E_6: f64 = 1318.51;
|
||||
}
|
||||
|
||||
pub fn freq_to_period(freq: f64) -> Duration {
|
||||
Duration::from_secs(1).div_f64(freq)
|
||||
}
|
||||
|
||||
pub fn play(freq: f64) -> Result<(), Error> {
|
||||
// set freq
|
||||
let period = freq_to_period(freq);
|
||||
let period_bytes = std::fs::read("/sys/class/pwm/pwmchip0/pwm0/period")?;
|
||||
if period_bytes == b"0\n" {
|
||||
std::fs::write("/sys/class/pwm/pwmchip0/pwm0/period", format!("{}", 1000))?;
|
||||
}
|
||||
std::fs::write("/sys/class/pwm/pwmchip0/pwm0/duty_cycle", "0")?;
|
||||
std::fs::write("/sys/class/pwm/pwmchip0/pwm0/period", format!("{}", period.as_nanos()))?;
|
||||
std::fs::write(
|
||||
"/sys/class/pwm/pwmchip0/pwm0/duty_cycle",
|
||||
format!("{}", (period / 2).as_nanos()),
|
||||
)?;
|
||||
// enable the thing
|
||||
std::fs::write("/sys/class/pwm/pwmchip0/pwm0/enable", "1")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn stop() -> Result<(), Error> {
|
||||
// disable the thing
|
||||
std::fs::write("/sys/class/pwm/pwmchip0/pwm0/enable", "0")?;
|
||||
// sleep small amount
|
||||
std::thread::sleep(Duration::from_micros(30));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn play_for_duration(freq: f64, duration: Duration) -> Result<(), Error> {
|
||||
play(freq)?;
|
||||
std::thread::sleep(duration);
|
||||
stop()
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Song<'a>(pub &'a [(Option<f64>, Duration)]);
|
||||
impl<'a> Song<'a> {
|
||||
pub fn play(&self) -> Result<(), Error> {
|
||||
for (note, duration) in self.0 {
|
||||
if let Some(note) = note {
|
||||
play_for_duration(*note, *duration)?;
|
||||
} else {
|
||||
std::thread::sleep(*duration);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Song<'static> {
|
||||
pub fn play_while<T, F: FnOnce() -> T>(&'static self, f: F) -> T {
|
||||
let run = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(true));
|
||||
let t_run = run.clone();
|
||||
let handle = std::thread::spawn(move || -> Result<(), Error> {
|
||||
while t_run.load(std::sync::atomic::Ordering::SeqCst) {
|
||||
self.play()?;
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
let res = f();
|
||||
run.store(false, std::sync::atomic::Ordering::SeqCst);
|
||||
let e = handle.join().unwrap().err();
|
||||
if let Some(e) = e {
|
||||
eprintln!("ERROR PLAYING SOUND: {}\n{:?}", e, e);
|
||||
}
|
||||
res
|
||||
}
|
||||
}
|
||||
impl<'a> From<&'a [(Option<f64>, Duration)]> for Song<'a> {
|
||||
fn from(t: &'a [(Option<f64>, Duration)]) -> Self {
|
||||
Song(t)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,37 @@
|
||||
#!/bin/sh
|
||||
#!/bin/bash
|
||||
|
||||
>&2 echo "As of 0.2.5, it is not possible to programmatically generate an Embassy image."
|
||||
>&2 echo "The image must be setup manually by copying over the artifacts, and installing the necessary dependencies."
|
||||
exit 1
|
||||
mv buster.img embassy.img
|
||||
product_key=$(cat product_key)
|
||||
loopdev=$(losetup -f -P embassy.img --show)
|
||||
root_mountpoint="/mnt/start9-${product_key}-root"
|
||||
boot_mountpoint="/mnt/start9-${product_key}-boot"
|
||||
mkdir -p "${root_mountpoint}"
|
||||
mkdir -p "${boot_mountpoint}"
|
||||
mount "${loopdev}p2" "${root_mountpoint}"
|
||||
mount "${loopdev}p1" "${boot_mountpoint}"
|
||||
echo "${product_key}" > "${root_mountpoint}/root/agent/product_key"
|
||||
echo -n "start9-" > "${root_mountpoint}/etc/hostname"
|
||||
echo -n "${product_key}" | shasum -t -a 256 | cut -c1-8 >> "${root_mountpoint}/etc/hostname"
|
||||
cat "${root_mountpoint}/etc/hosts" | grep -v "127.0.1.1" > "${root_mountpoint}/etc/hosts.tmp"
|
||||
echo -ne "127.0.1.1\tstart9-" >> "${root_mountpoint}/etc/hosts.tmp"
|
||||
echo -n "${product_key}" | shasum -t -a 256 | cut -c1-8 >> "${root_mountpoint}/etc/hosts.tmp"
|
||||
mv "${root_mountpoint}/etc/hosts.tmp" "${root_mountpoint}/etc/hosts"
|
||||
cp agent/dist/agent "${root_mountpoint}/usr/local/bin/agent"
|
||||
chmod 700 "${root_mountpoint}/usr/local/bin/agent"
|
||||
cp appmgr/target/armv7-unknown-linux-musleabihf/release/appmgr "${root_mountpoint}/usr/local/bin/appmgr"
|
||||
chmod 700 "${root_mountpoint}/usr/local/bin/appmgr"
|
||||
cp lifeline/target/armv7-unknown-linux-musleabihf/release/lifeline "${root_mountpoint}/usr/local/bin/lifeline"
|
||||
chmod 700 "${root_mountpoint}/usr/local/bin/lifeline"
|
||||
cp docker-daemon.json "${root_mountpoint}/etc/docker/daemon.json"
|
||||
cp setup.sh "${root_mountpoint}/root/setup.sh"
|
||||
chmod 700 "${root_mountpoint}/root/setup.sh"
|
||||
cp setup.service /etc/systemd/system/setup.service
|
||||
cp lifeline/lifeline.service /etc/systemd/system/lifeline.service
|
||||
cp agent/config/agent.service /etc/systemd/system/agent.service
|
||||
cat "${boot_mountpoint}/config.txt" | grep -v "dtoverlay=pwm-2chan" > "${boot_mountpoint}/config.txt.tmp"
|
||||
echo "dtoverlay=pwm-2chan" >> "${boot_mountpoint}/config.txt.tmp"
|
||||
umount "${root_mountpoint}"
|
||||
rm -r "${root_mountpoint}"
|
||||
umount "${boot_mountpoint}"
|
||||
rm -r "${boot_mountpoint}"
|
||||
losetup -d ${loopdev}
|
||||
10
setup.service
Normal file
10
setup.service
Normal file
@@ -0,0 +1,10 @@
|
||||
[Unit]
|
||||
Description=Boot process for system setup.
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/root/setup.sh
|
||||
RemainAfterExit=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
21
setup.sh
Normal file
21
setup.sh
Normal file
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
apt update
|
||||
apt install -y libsecp256k1-0
|
||||
apt install -y tor
|
||||
apt install -y docker.io
|
||||
apt install -y iotop
|
||||
apt install -y bmon
|
||||
apt autoremove -y
|
||||
mkdir -p /root/volumes
|
||||
mkdir -p /root/tmp/appmgr
|
||||
mkdir -p /root/agent
|
||||
mkdir -p /root/appmgr/tor
|
||||
systemctl enable lifeline
|
||||
systemctl enable agent
|
||||
systemctl enable ssh
|
||||
systemctl enable avahi-daemon
|
||||
passwd -l root
|
||||
passwd -l pi
|
||||
sync
|
||||
systemctl disable setup.service
|
||||
reboot
|
||||
@@ -6,14 +6,10 @@
|
||||
|
||||
`npm i -g @ionic/cli`
|
||||
|
||||
`git clone https://github.com/Start9Labs/embassy-ui.git`
|
||||
`git clone https://github.com/Start9Labs/embassy-os.git`
|
||||
|
||||
`cd embassy-ui`
|
||||
`cd embassy-os/ui`
|
||||
|
||||
`npm i`
|
||||
|
||||
`ionic serve`
|
||||
|
||||
## Production Deployment
|
||||
|
||||
`ionic build --prod`
|
||||
|
||||
@@ -29,6 +29,11 @@
|
||||
"glob": "**/*.svg",
|
||||
"input": "node_modules/ionicons/dist/ionicons/svg",
|
||||
"output": "./svg"
|
||||
},
|
||||
{
|
||||
"glob": "**/*.svg",
|
||||
"input": "src/assets/icon",
|
||||
"output": "./svg"
|
||||
}
|
||||
],
|
||||
"styles": [
|
||||
@@ -52,7 +57,6 @@
|
||||
"optimization": true,
|
||||
"outputHashing": "all",
|
||||
"sourceMap": false,
|
||||
"extractCss": true,
|
||||
"namedChunks": false,
|
||||
"aot": true,
|
||||
"extractLicenses": true,
|
||||
|
||||
@@ -10,14 +10,6 @@ rm -rf www
|
||||
echo "FILTER: ionic build"
|
||||
npm run build-prod
|
||||
|
||||
echo "FILTER: cp client-manifest.yaml www"
|
||||
cp client-manifest.yaml www
|
||||
|
||||
echo "FILTER: git hash"
|
||||
touch git-hash.txt
|
||||
git log | head -n1 > git-hash.txt
|
||||
mv git-hash.txt www
|
||||
|
||||
echo "FILTER: ssh + rm -rf /var/www/html/start9-ambassador/"
|
||||
ssh root@start9-$1.local "rm -rf /var/www/html/start9-ambassador"
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
manifest-version: 0
|
||||
app-id: start9-ambassador
|
||||
app-version: 0.2.6
|
||||
app-version: 0.2.8
|
||||
uri-rewrites:
|
||||
- =/api -> http://{{start9-ambassador}}:5959/authenticate
|
||||
- /api/ -> http://{{start9-ambassador}}:5959/
|
||||
|
||||
2132
ui/package-lock.json
generated
2132
ui/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "embassy-ui",
|
||||
"version": "0.2.6",
|
||||
"version": "0.2.8",
|
||||
"description": "GUI for EmbassyOS",
|
||||
"author": "Start9 Labs",
|
||||
"homepage": "https://github.com/Start9Labs/embassy-ui",
|
||||
@@ -8,19 +8,19 @@
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
"build": "ng build",
|
||||
"build-prod": "ng build --prod && tsc postprocess.ts && node postprocess.js",
|
||||
"build-prod": "ng build --prod && tsc postprocess.ts && node postprocess.js && cp client-manifest.yaml www && git log | head -n1 > www/git-hash.txt",
|
||||
"test": "ng test",
|
||||
"lint": "ng lint",
|
||||
"e2e": "ng e2e"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/common": "^10.1.6",
|
||||
"@angular/core": "^10.1.6",
|
||||
"@angular/forms": "^10.1.6",
|
||||
"@angular/platform-browser": "^10.1.6",
|
||||
"@angular/platform-browser-dynamic": "^10.1.6",
|
||||
"@angular/router": "^10.1.6",
|
||||
"@angular/common": "^11.0.0",
|
||||
"@angular/core": "^11.0.0",
|
||||
"@angular/forms": "^11.0.0",
|
||||
"@angular/platform-browser": "^11.0.0",
|
||||
"@angular/platform-browser-dynamic": "^11.0.0",
|
||||
"@angular/router": "^11.0.0",
|
||||
"@ionic/angular": "^5.4.0",
|
||||
"@ionic/storage": "2.2.0",
|
||||
"@start9labs/emver": "^0.1.1",
|
||||
@@ -42,19 +42,19 @@
|
||||
"zone.js": "^0.11.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^0.1002.0",
|
||||
"@angular/cli": "^10.1.7",
|
||||
"@angular/compiler": "^10.1.6",
|
||||
"@angular/compiler-cli": "^10.1.6",
|
||||
"@angular/language-service": "^10.1.6",
|
||||
"@ionic/angular-toolkit": "^2.3.3",
|
||||
"@angular-devkit/build-angular": "^0.1100.0",
|
||||
"@angular/cli": "^11.0.0",
|
||||
"@angular/compiler": "^11.0.0",
|
||||
"@angular/compiler-cli": "^11.0.0",
|
||||
"@angular/language-service": "^11.0.0",
|
||||
"@ionic/angular-toolkit": "^3.0.0",
|
||||
"@ionic/lab": "^3.2.9",
|
||||
"@types/json-pointer": "^1.0.30",
|
||||
"@types/marked": "^1.1.0",
|
||||
"@types/node": "^14.11.10",
|
||||
"@types/uuid": "^8.0.0",
|
||||
"node-html-parser": "^1.3.1",
|
||||
"ts-node": "^9.0.0",
|
||||
"node-html-parser": "^2.0.0",
|
||||
"ts-node": "^9.1.0",
|
||||
"tslint": "^6.1.0",
|
||||
"typescript": "4.0.5"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {
|
||||
ValueSpec, ConfigSpec, UniqueBy, ValueSpecOf, ValueType
|
||||
ValueSpec, ConfigSpec, UniqueBy, ValueSpecOf, ValueType, ValueSpecObject, ValueSpecUnion
|
||||
} from './config-types'
|
||||
import * as pointer from 'json-pointer'
|
||||
import * as handlebars from 'handlebars'
|
||||
@@ -207,7 +207,8 @@ export class ConfigCursor<T extends ValueType> {
|
||||
}
|
||||
case 'enum':
|
||||
if (typeof cfg === 'string') {
|
||||
return spec.values.includes(cfg) ? null : `${cfg} is not a valid selection.`
|
||||
spec.valuesSet = spec.valuesSet || new Set(spec.values)
|
||||
return spec.valuesSet.has(cfg) ? null : `${cfg} is not a valid selection.`
|
||||
} else {
|
||||
throw new TypeError(`${this.ptr}: expected string, got ${Array.isArray(cfg) ? 'array' : typeof cfg}`)
|
||||
}
|
||||
@@ -223,14 +224,22 @@ export class ConfigCursor<T extends ValueType> {
|
||||
if (max && length > max) {
|
||||
return spec.subtype === 'enum' ? 'Too many options selected.' : 'List is too long.'
|
||||
}
|
||||
for (let idx in cfg) {
|
||||
for (let idx = 0; idx < cfg.length; idx++) {
|
||||
let cursor = this.seekNext(idx)
|
||||
if (cursor.checkInvalid()) {
|
||||
return `Item #${idx + 1} is invalid. ${cursor.checkInvalid()}`
|
||||
const invalid = cursor.checkInvalid()
|
||||
if (invalid) {
|
||||
return `Item #${idx + 1} is invalid. ${invalid}.`
|
||||
}
|
||||
for (let idx2 in cfg) {
|
||||
if (idx !== idx2 && cursor.equals(this.seekNext(idx2))) {
|
||||
return `Item #${idx + 1} is not unique.`
|
||||
if (spec.subtype === 'enum') continue
|
||||
for (let idx2 = idx + 1; idx2 < cfg.length; idx2++) {
|
||||
if (cursor.equals(this.seekNext(idx2))) {
|
||||
return `Item #${idx + 1} is not unique.` + ('uniqueBy' in cursor.spec()) ? `${
|
||||
displayUniqueBy(
|
||||
(cursor.spec() as ValueSpecObject | ValueSpecUnion).uniqueBy,
|
||||
(cursor.spec() as ValueSpecObject | ValueSpecUnion),
|
||||
cursor.config()
|
||||
)
|
||||
} must be unique.` : ''
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -443,4 +452,34 @@ function isEqual (uniqueBy: UniqueBy, lhs: ConfigCursor<'object'>, rhs: ConfigCu
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
export function displayUniqueBy(uniqueBy: UniqueBy, spec: ValueSpecObject | ValueSpecUnion, value: object): string {
|
||||
if (typeof uniqueBy === 'string') {
|
||||
if (spec.type === 'object') {
|
||||
return spec.spec[uniqueBy].name
|
||||
} else if (spec.type === 'union') {
|
||||
if (uniqueBy === spec.tag.id) {
|
||||
return spec.tag.name
|
||||
} else {
|
||||
return spec.variants[value[spec.tag.id]][uniqueBy].name
|
||||
}
|
||||
}
|
||||
} else if ('any' in uniqueBy) {
|
||||
return uniqueBy.any.map(uq => {
|
||||
if (typeof uq === 'object' && 'all' in uq) {
|
||||
return `(${displayUniqueBy(uq, spec, value)})`
|
||||
} else {
|
||||
return displayUniqueBy(uq, spec, value)
|
||||
}
|
||||
}).join(' and ')
|
||||
} else if ('all' in uniqueBy) {
|
||||
return uniqueBy.all.map(uq => {
|
||||
if (typeof uq === 'object' && 'any' in uq) {
|
||||
return `(${displayUniqueBy(uq, spec, value)})`
|
||||
} else {
|
||||
return displayUniqueBy(uq, spec, value)
|
||||
}
|
||||
}).join(' or ')
|
||||
}
|
||||
}
|
||||
@@ -102,6 +102,7 @@ export interface ListValueSpecNumber {
|
||||
|
||||
export interface ListValueSpecEnum {
|
||||
values: string[]
|
||||
valuesSet?: Set<string>
|
||||
valueNames: { [value: string]: string }
|
||||
}
|
||||
|
||||
|
||||
@@ -33,13 +33,18 @@ const routes: Routes = [
|
||||
canActivateChild: [AuthGuard],
|
||||
loadChildren: () => import('./pages/apps-routes/apps-routing.module').then(m => m.AppsRoutingModule),
|
||||
},
|
||||
// {
|
||||
// path: 'drives',
|
||||
// canActivate: [AuthGuard],
|
||||
// loadChildren: () => import('./pages/server-routes/external-drives/external-drives.module').then( m => m.ExternalDrivesPageModule),
|
||||
// },
|
||||
]
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forRoot(routes, {
|
||||
preloadingStrategy: PreloadAllModules,
|
||||
initialNavigation: false,
|
||||
initialNavigation: 'disabled',
|
||||
useHash: true,
|
||||
}),
|
||||
],
|
||||
|
||||
@@ -96,6 +96,7 @@
|
||||
<ion-icon name="power"></ion-icon>
|
||||
<ion-icon name="pulse"></ion-icon>
|
||||
<ion-icon name="qr-code-outline"></ion-icon>
|
||||
<ion-icon name="globe-outline"></ion-icon>
|
||||
<ion-icon name="reload-outline"></ion-icon>
|
||||
<ion-icon name="refresh-outline"></ion-icon>
|
||||
<ion-icon name="save-outline"></ion-icon>
|
||||
@@ -128,12 +129,10 @@
|
||||
<ion-infinite-scroll-content loadingSpinner="lines"></ion-infinite-scroll-content>
|
||||
</ion-content>
|
||||
<ion-input></ion-input>
|
||||
<ion-input type="password">getdots</ion-input>
|
||||
<ion-input *ngIf="untilLoaded" type="password" value="getdots"></ion-input>
|
||||
<ion-item></ion-item>
|
||||
<ion-item-divider></ion-item-divider>
|
||||
<ion-item-group></ion-item-group>
|
||||
<ion-item-options></ion-item-options>
|
||||
<ion-item-sliding></ion-item-sliding>
|
||||
<ion-label></ion-label>
|
||||
<ion-list></ion-list>
|
||||
<ion-loading></ion-loading>
|
||||
@@ -149,6 +148,7 @@
|
||||
<ion-spinner name="dots"></ion-spinner>
|
||||
<ion-spinner name="lines"></ion-spinner>
|
||||
<ion-text></ion-text>
|
||||
<ion-text style="font-weight: bold">load bold</ion-text>
|
||||
<ion-textarea></ion-textarea>
|
||||
<ion-title></ion-title>
|
||||
<ion-toast></ion-toast>
|
||||
|
||||
@@ -13,6 +13,7 @@ import { LoaderService } from './services/loader.service'
|
||||
import { Emver } from './services/emver.service'
|
||||
import { SplitPaneTracker } from './services/split-pane.service'
|
||||
import { LoadingOptions } from '@ionic/core'
|
||||
import { pauseFor } from './util/misc.util'
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
@@ -26,6 +27,7 @@ export class AppComponent {
|
||||
serverName$ : Observable<string>
|
||||
serverBadge$: Observable<number>
|
||||
selectedIndex = 0
|
||||
untilLoaded = true
|
||||
appPages = [
|
||||
{
|
||||
title: 'Services',
|
||||
@@ -47,6 +49,11 @@ export class AppComponent {
|
||||
url: '/notifications',
|
||||
icon: 'notifications-outline',
|
||||
},
|
||||
// {
|
||||
// title: 'Backup drives',
|
||||
// url: '/drives',
|
||||
// icon: 'albums-outline',
|
||||
// },
|
||||
]
|
||||
|
||||
constructor (
|
||||
@@ -69,6 +76,12 @@ export class AppComponent {
|
||||
this.init()
|
||||
}
|
||||
|
||||
ionViewDidEnter () {
|
||||
// weird bug where a browser grabbed the value 'getdots' from the app.component.html preload input field.
|
||||
// this removes that field after prleloading occurs.
|
||||
pauseFor(500).then(() => this.untilLoaded = false)
|
||||
}
|
||||
|
||||
async init () {
|
||||
let fromFresh = true
|
||||
await this.storage.ready()
|
||||
@@ -175,7 +188,7 @@ export class AppComponent {
|
||||
await alert.present()
|
||||
}
|
||||
|
||||
splitPaneVisible (e) {
|
||||
splitPaneVisible (e: any) {
|
||||
this.splitPane.$menuFixedOpenOnLeft$.next(e.detail.visible)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { NgModule, CUSTOM_ELEMENTS_SCHEMA, Type } from '@angular/core'
|
||||
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'
|
||||
import { BrowserModule } from '@angular/platform-browser'
|
||||
import { RouteReuseStrategy } from '@angular/router'
|
||||
import { IonicModule, IonicRouteStrategy } from '@ionic/angular'
|
||||
@@ -15,6 +15,7 @@ import { ConfigService } from './services/config.service'
|
||||
import { QRCodeModule } from 'angularx-qrcode'
|
||||
import { APP_CONFIG_COMPONENT_MAPPING } from './modals/app-config-injectable/modal-injectable-token'
|
||||
import { appConfigComponents } from './modals/app-config-injectable/modal-injectable-value';
|
||||
import { OSWelcomePageModule } from './modals/os-welcome/os-welcome.module'
|
||||
|
||||
@NgModule({
|
||||
declarations: [AppComponent],
|
||||
@@ -26,6 +27,7 @@ import { appConfigComponents } from './modals/app-config-injectable/modal-inject
|
||||
AppRoutingModule,
|
||||
IonicStorageModule.forRoot(),
|
||||
QRCodeModule,
|
||||
OSWelcomePageModule,
|
||||
],
|
||||
providers: [
|
||||
{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy },
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<!-- TODO: EJECT-DISKS, add a check box to allow a user to eject a disk on backup completion. -->
|
||||
<ion-content>
|
||||
<div style="height: 85%; margin: 20px; display: flex; flex-direction: column; justify-content: space-between;">
|
||||
<div>
|
||||
<h4><ion-text color="dark">Ready to Backup</ion-text></h4>
|
||||
<p><ion-text color="medium">Enter your master password to create an encrypted backup.</ion-text></p>
|
||||
</div>
|
||||
<div>
|
||||
<ion-item lines="none" style="--background: var(--ion-background-color); --border-color: var(--ion-color-medium);">
|
||||
<ion-label style="font-size: small" position="floating">Master Password</ion-label>
|
||||
<ion-input style="border-style: solid; border-width: 0px 0px 1px 0px; border-color: var(--ion-color-dark);" [(ngModel)]="password" type="password" (ionChange)="$error$.next('')"></ion-input>
|
||||
</ion-item>
|
||||
<ion-item *ngIf="$error$ | async as e" lines="none" style="--background: var(--ion-background-color);">
|
||||
<ion-label style="font-size: small" color="danger" class="ion-text-wrap">{{e}}</ion-label>
|
||||
</ion-item>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: flex-end; align-items: center;">
|
||||
<ion-button fill="clear" color="medium" (click)="cancel()">
|
||||
Cancel
|
||||
</ion-button>
|
||||
<ion-button fill="clear" color="primary" (click)="submit()">
|
||||
Create Backup
|
||||
</ion-button>
|
||||
</div>
|
||||
</div>
|
||||
</ion-content>
|
||||
@@ -0,0 +1,22 @@
|
||||
import { NgModule } from '@angular/core'
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { AppBackupConfirmationComponent } from './app-backup-confirmation.component'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { RouterModule } from '@angular/router'
|
||||
import { SharingModule } from 'src/app/modules/sharing.module'
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AppBackupConfirmationComponent,
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
RouterModule.forChild([]),
|
||||
SharingModule,
|
||||
FormsModule,
|
||||
],
|
||||
exports: [AppBackupConfirmationComponent],
|
||||
})
|
||||
export class AppBackupConfirmationComponentModule { }
|
||||
@@ -0,0 +1,45 @@
|
||||
import { Component, Input, OnInit } from '@angular/core'
|
||||
import { ModalController } from '@ionic/angular'
|
||||
import { BehaviorSubject } from 'rxjs'
|
||||
import { AppInstalledFull } from 'src/app/models/app-types'
|
||||
import { DiskPartition } from 'src/app/models/server-model'
|
||||
|
||||
@Component({
|
||||
selector: 'app-backup-confirmation',
|
||||
templateUrl: './app-backup-confirmation.component.html',
|
||||
styleUrls: ['./app-backup-confirmation.component.scss'],
|
||||
})
|
||||
export class AppBackupConfirmationComponent implements OnInit {
|
||||
unmasked = false
|
||||
password: string
|
||||
$error$: BehaviorSubject<string> = new BehaviorSubject('')
|
||||
|
||||
// TODO: EJECT-DISKS pass this through the modalCtrl once ejecting disks is an option in the UI.
|
||||
eject = true
|
||||
message: string
|
||||
|
||||
@Input() app: AppInstalledFull
|
||||
@Input() partition: DiskPartition
|
||||
|
||||
constructor (private readonly modalCtrl: ModalController) { }
|
||||
ngOnInit () {
|
||||
this.message = `Enter your master password to create an encrypted backup of ${this.app.title} to "${this.partition.label || this.partition.logicalname}".`
|
||||
}
|
||||
|
||||
toggleMask () {
|
||||
this.unmasked = !this.unmasked
|
||||
}
|
||||
|
||||
cancel () {
|
||||
this.modalCtrl.dismiss({ cancel: true })
|
||||
}
|
||||
|
||||
submit () {
|
||||
if (!this.password || this.password.length < 12) {
|
||||
this.$error$.next('Password must be at least 12 characters in length.')
|
||||
return
|
||||
}
|
||||
const { password } = this
|
||||
this.modalCtrl.dismiss({ password })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<div class="slide-content">
|
||||
<div style="margin-top: 25px;">
|
||||
<div style="margin: 15px; display: flex; justify-content: center; align-items: center;">
|
||||
<ion-label [color]="$color$ | async" style="font-size: xx-large; font-weight: bold;">
|
||||
Warning
|
||||
</ion-label>
|
||||
</div>
|
||||
<div class="long-message">
|
||||
{{params.developerNotes}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,20 @@
|
||||
import { NgModule } from '@angular/core'
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { DeveloperNotesComponent } from './developer-notes.component'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { RouterModule } from '@angular/router'
|
||||
import { SharingModule } from 'src/app/modules/sharing.module'
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
DeveloperNotesComponent,
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
RouterModule.forChild([]),
|
||||
SharingModule,
|
||||
],
|
||||
exports: [DeveloperNotesComponent],
|
||||
})
|
||||
export class DeveloperNotesComponentModule { }
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Component, Input, OnInit } from '@angular/core'
|
||||
import { BehaviorSubject, Subject } from 'rxjs'
|
||||
import { Colorable, Loadable } from '../loadable'
|
||||
import { WizardAction } from '../wizard-types'
|
||||
|
||||
@Component({
|
||||
selector: 'developer-notes',
|
||||
templateUrl: './developer-notes.component.html',
|
||||
styleUrls: ['../install-wizard.component.scss'],
|
||||
})
|
||||
export class DeveloperNotesComponent implements OnInit, Loadable, Colorable {
|
||||
@Input() params: {
|
||||
action: WizardAction
|
||||
developerNotes: string
|
||||
}
|
||||
|
||||
$loading$ = new BehaviorSubject(false)
|
||||
$color$ = new BehaviorSubject('warning')
|
||||
$cancel$ = new Subject<void>()
|
||||
|
||||
load () { }
|
||||
|
||||
constructor () { }
|
||||
ngOnInit () {
|
||||
console.log('Developer Notes', this.params)
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@
|
||||
<ion-slides *ngIf="!($error$ | async)" id="slide-show" style="--bullet-background: white" pager="false">
|
||||
<ion-slide *ngFor="let slide of params.slideDefinitions">
|
||||
<dependencies #components *ngIf="slide.selector === 'dependencies'" [params]="slide.params"></dependencies>
|
||||
<developer-notes #components *ngIf="slide.selector === 'developer-notes'" [params]="slide.params"></developer-notes>
|
||||
<dependents #components *ngIf="slide.selector === 'dependents'" [params]="slide.params" [finished]="finished"></dependents>
|
||||
<complete #components *ngIf="slide.selector === 'complete'" [params]="slide.params" [finished]="finished"></complete>
|
||||
</ion-slide>
|
||||
|
||||
@@ -7,6 +7,7 @@ import { SharingModule } from 'src/app/modules/sharing.module'
|
||||
import { DependenciesComponentModule } from './dependencies/dependencies.component.module'
|
||||
import { DependentsComponentModule } from './dependents/dependents.component.module'
|
||||
import { CompleteComponentModule } from './complete/complete.component.module'
|
||||
import { DeveloperNotesComponentModule } from './developer-notes/developer-notes.component.module'
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@@ -20,6 +21,7 @@ import { CompleteComponentModule } from './complete/complete.component.module'
|
||||
DependenciesComponentModule,
|
||||
DependentsComponentModule,
|
||||
CompleteComponentModule,
|
||||
DeveloperNotesComponentModule,
|
||||
],
|
||||
exports: [InstallWizardComponent],
|
||||
})
|
||||
|
||||
@@ -7,6 +7,7 @@ import { capitalizeFirstLetter } from 'src/app/util/misc.util'
|
||||
import { CompleteComponent } from './complete/complete.component'
|
||||
import { DependenciesComponent } from './dependencies/dependencies.component'
|
||||
import { DependentsComponent } from './dependents/dependents.component'
|
||||
import { DeveloperNotesComponent } from './developer-notes/developer-notes.component'
|
||||
import { Colorable, Loadable } from './loadable'
|
||||
import { WizardAction } from './wizard-types'
|
||||
|
||||
@@ -80,22 +81,26 @@ export class InstallWizardComponent extends Cleanup implements OnInit {
|
||||
private async slide () {
|
||||
if (this.slideComponents[this.slideIndex + 1] === undefined) { return this.finished({ final: true }) }
|
||||
this.slideIndex += 1
|
||||
await this.slideContainer.lockSwipes(false)
|
||||
await Promise.all([this.contentContainer.scrollToTop(), this.slideContainer.slideNext()])
|
||||
await this.slideContainer.lockSwipes(true)
|
||||
this.currentSlide.load()
|
||||
await this.slideContainer.lockSwipes(false)
|
||||
await Promise.all([
|
||||
this.contentContainer.scrollToTop(),
|
||||
this.slideContainer.slideNext(500),
|
||||
])
|
||||
await this.slideContainer.lockSwipes(true)
|
||||
this.slideContainer.update()
|
||||
}
|
||||
}
|
||||
|
||||
export interface SlideCommon {
|
||||
selector: string
|
||||
selector: string // component http selector
|
||||
cancelButton: {
|
||||
// indicates the existence of a cancel button, and whether to have text or an icon 'x' by default.
|
||||
afterLoading?: { text?: string },
|
||||
whileLoading?: { text?: string }
|
||||
}
|
||||
nextButton?: string,
|
||||
finishButton?: string
|
||||
nextButton?: string, // existence and content of next button
|
||||
finishButton?: string // existence and content of finish button
|
||||
}
|
||||
|
||||
export type SlideDefinition = SlideCommon & (
|
||||
@@ -108,6 +113,9 @@ export type SlideDefinition = SlideCommon & (
|
||||
} | {
|
||||
selector: 'complete',
|
||||
params: CompleteComponent['params']
|
||||
} | {
|
||||
selector: 'developer-notes',
|
||||
params: DeveloperNotesComponent['params']
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { AppModel, AppStatus } from 'src/app/models/app-model'
|
||||
import { exists } from 'src/app/util/misc.util'
|
||||
import { AppDependency, DependentBreakage, AppInstalledPreview } from '../../models/app-types'
|
||||
import { ApiService } from '../../services/api/api.service'
|
||||
import { InstallWizardComponent, SlideDefinition, TopbarParams } from './install-wizard.component'
|
||||
@@ -9,9 +10,9 @@ export class WizardBaker {
|
||||
constructor (private readonly apiService: ApiService, private readonly appModel: AppModel) { }
|
||||
|
||||
install (values: {
|
||||
id: string, title: string, version: string, serviceRequirements: AppDependency[]
|
||||
id: string, title: string, version: string, serviceRequirements: AppDependency[], installAlert?: string
|
||||
}): InstallWizardComponent['params'] {
|
||||
const { id, title, version, serviceRequirements } = values
|
||||
const { id, title, version, serviceRequirements, installAlert } = values
|
||||
|
||||
validate(id, exists, 'missing id')
|
||||
validate(title, exists, 'missing title')
|
||||
@@ -22,6 +23,9 @@ export class WizardBaker {
|
||||
const toolbar: TopbarParams = { action, title, version }
|
||||
|
||||
const slideDefinitions: SlideDefinition[] = [
|
||||
installAlert ? { selector: 'developer-notes', cancelButton: { afterLoading: { text: 'Cancel' } }, nextButton: 'Next', params: {
|
||||
action, developerNotes: installAlert,
|
||||
}} : undefined,
|
||||
{ selector: 'dependencies', cancelButton: { afterLoading: { text: 'Cancel' } }, nextButton: 'Install', params: {
|
||||
action, title, version, serviceRequirements,
|
||||
}},
|
||||
@@ -31,13 +35,14 @@ export class WizardBaker {
|
||||
}),
|
||||
}},
|
||||
]
|
||||
return { toolbar, slideDefinitions }
|
||||
return { toolbar, slideDefinitions: slideDefinitions.filter(exists) }
|
||||
}
|
||||
|
||||
update (values: {
|
||||
id: string, title: string, version: string, serviceRequirements: AppDependency[]
|
||||
id: string, title: string, version: string, serviceRequirements: AppDependency[], installAlert?: string
|
||||
}): InstallWizardComponent['params'] {
|
||||
const { id, title, version, serviceRequirements } = values
|
||||
const { id, title, version, serviceRequirements, installAlert } = values
|
||||
|
||||
validate(id, exists, 'missing id')
|
||||
validate(title, exists, 'missing title')
|
||||
validate(version, exists, 'missing version')
|
||||
@@ -47,6 +52,9 @@ export class WizardBaker {
|
||||
const toolbar: TopbarParams = { action, title, version }
|
||||
|
||||
const slideDefinitions: SlideDefinition[] = [
|
||||
installAlert ? { selector: 'developer-notes', cancelButton: { afterLoading: { text: 'Cancel' } }, nextButton: 'Next', params: {
|
||||
action, developerNotes: installAlert,
|
||||
}} : undefined,
|
||||
{ selector: 'dependencies', cancelButton: { afterLoading: { text: 'Cancel' } }, nextButton: 'Update', params: {
|
||||
action, title, version, serviceRequirements,
|
||||
}},
|
||||
@@ -59,13 +67,13 @@ export class WizardBaker {
|
||||
}),
|
||||
}},
|
||||
]
|
||||
return { toolbar, slideDefinitions }
|
||||
return { toolbar, slideDefinitions: slideDefinitions.filter(exists) }
|
||||
}
|
||||
|
||||
downgrade (values: {
|
||||
id: string, title: string, version: string, serviceRequirements: AppDependency[]
|
||||
id: string, title: string, version: string, serviceRequirements: AppDependency[], installAlert?: string
|
||||
}): InstallWizardComponent['params'] {
|
||||
const { id, title, version, serviceRequirements } = values
|
||||
const { id, title, version, serviceRequirements, installAlert } = values
|
||||
|
||||
validate(id, exists, 'missing id')
|
||||
validate(title, exists, 'missing title')
|
||||
@@ -76,6 +84,9 @@ export class WizardBaker {
|
||||
const toolbar: TopbarParams = { action, title, version }
|
||||
|
||||
const slideDefinitions: SlideDefinition[] = [
|
||||
installAlert ? { selector: 'developer-notes', cancelButton: { afterLoading: { text: 'Cancel' } }, nextButton: 'Next', params: {
|
||||
action, developerNotes: installAlert,
|
||||
}} : undefined,
|
||||
{ selector: 'dependencies', cancelButton: { afterLoading: { text: 'Cancel' } }, nextButton: 'Downgrade', params: {
|
||||
action, title, version, serviceRequirements,
|
||||
}},
|
||||
@@ -88,13 +99,13 @@ export class WizardBaker {
|
||||
}),
|
||||
}},
|
||||
]
|
||||
return { toolbar, slideDefinitions }
|
||||
return { toolbar, slideDefinitions: slideDefinitions.filter(exists) }
|
||||
}
|
||||
|
||||
uninstall (values: {
|
||||
id: string, title: string, version: string
|
||||
id: string, title: string, version: string, uninstallAlert?: string
|
||||
}): InstallWizardComponent['params'] {
|
||||
const { id, title, version } = values
|
||||
const { id, title, version, uninstallAlert } = values
|
||||
|
||||
validate(id, exists, 'missing id')
|
||||
validate(title, exists, 'missing title')
|
||||
@@ -104,6 +115,9 @@ export class WizardBaker {
|
||||
const toolbar: TopbarParams = { action, title, version }
|
||||
|
||||
const slideDefinitions: SlideDefinition[] = [
|
||||
{ selector: 'developer-notes', cancelButton: { afterLoading: { text: 'Cancel' } }, nextButton: 'Continue', params: {
|
||||
action, developerNotes: uninstallAlert || defaultUninstallationWarning(title) },
|
||||
},
|
||||
{ selector: 'dependents', cancelButton: { whileLoading: { }, afterLoading: { text: 'Cancel' } }, nextButton: 'Uninstall', params: {
|
||||
action, verb: 'uninstalling', title, fetchBreakages: () => this.apiService.uninstallApp(id, true).then( ({ breakages }) => breakages ),
|
||||
}},
|
||||
@@ -111,7 +125,7 @@ export class WizardBaker {
|
||||
action, verb: 'uninstalling', title, executeAction: () => this.apiService.uninstallApp(id).then(() => this.appModel.delete(id)),
|
||||
}},
|
||||
]
|
||||
return { toolbar, slideDefinitions }
|
||||
return { toolbar, slideDefinitions: slideDefinitions.filter(exists) }
|
||||
}
|
||||
|
||||
stop (values: {
|
||||
@@ -158,4 +172,5 @@ function validate<T> (t: T, test: (t: T) => Boolean, desc: string) {
|
||||
}
|
||||
}
|
||||
|
||||
const exists = t => !!t
|
||||
|
||||
const defaultUninstallationWarning = serviceName => `Uninstalling ${ serviceName } will result in the deletion of its data.`
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<ion-item button lines="none" *ngIf="updateAvailable$ | async as version" (click)="confirmUpdate(version)">
|
||||
<ion-label>
|
||||
New EmbassyOS Version {{version | displayEmver}} Available!
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
@@ -0,0 +1,18 @@
|
||||
import { NgModule } from '@angular/core'
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { UpdateOsBannerComponent } from './update-os-banner.component'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { SharingModule } from 'src/app/modules/sharing.module'
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
UpdateOsBannerComponent,
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
SharingModule,
|
||||
],
|
||||
exports: [UpdateOsBannerComponent],
|
||||
})
|
||||
export class UpdateOsBannerComponentModule { }
|
||||
@@ -0,0 +1,11 @@
|
||||
ion-item {
|
||||
|
||||
--background: linear-gradient(90deg, var(--ion-color-light), var(--ion-color-primary));
|
||||
--min-height: 0px;
|
||||
ion-label {
|
||||
font-family: 'Open Sans';
|
||||
font-size: small;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { Component } from '@angular/core'
|
||||
import { OsUpdateService } from 'src/app/services/os-update.service'
|
||||
import { Observable } from 'rxjs'
|
||||
import { AlertController } from '@ionic/angular'
|
||||
import { LoaderService } from 'src/app/services/loader.service'
|
||||
import { displayEmver } from 'src/app/pipes/emver.pipe'
|
||||
|
||||
@Component({
|
||||
selector: 'update-os-banner',
|
||||
templateUrl: './update-os-banner.component.html',
|
||||
styleUrls: ['./update-os-banner.component.scss'],
|
||||
})
|
||||
export class UpdateOsBannerComponent {
|
||||
updateAvailable$: Observable<undefined | string>
|
||||
constructor (
|
||||
private readonly osUpdateService: OsUpdateService,
|
||||
private readonly alertCtrl: AlertController,
|
||||
private readonly loader: LoaderService,
|
||||
) {
|
||||
this.updateAvailable$ = this.osUpdateService.watchForUpdateAvailable$()
|
||||
}
|
||||
|
||||
ngOnInit () { }
|
||||
|
||||
async confirmUpdate (versionLatest: string) {
|
||||
const alert = await this.alertCtrl.create({
|
||||
header: `Update EmbassyOS`,
|
||||
message: `Update EmbassyOS to version ${displayEmver(versionLatest)}?`,
|
||||
buttons: [
|
||||
{
|
||||
text: 'Cancel',
|
||||
role: 'cancel',
|
||||
},
|
||||
{
|
||||
text: 'Update',
|
||||
handler: () => this.update(versionLatest),
|
||||
},
|
||||
],
|
||||
})
|
||||
await alert.present()
|
||||
}
|
||||
|
||||
private async update (versionLatest: string) {
|
||||
return this.loader.displayDuringP(
|
||||
this.osUpdateService.updateEmbassyOS(versionLatest),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,14 @@ import { NgModule } from '@angular/core'
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { AppBackupPage } from './app-backup.page'
|
||||
import { AppBackupConfirmationComponentModule } from 'src/app/components/app-backup-confirmation/app-backup-confirmation.component.module'
|
||||
|
||||
@NgModule({
|
||||
declarations: [AppBackupPage],
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
AppBackupConfirmationComponentModule,
|
||||
],
|
||||
entryComponents: [AppBackupPage],
|
||||
exports: [AppBackupPage],
|
||||
|
||||
@@ -27,14 +27,25 @@
|
||||
<ion-spinner *ngIf="loading" class="center" name="lines" color="warning"></ion-spinner>
|
||||
|
||||
<ng-container *ngIf="!loading">
|
||||
<ion-item *ngIf="type === 'restore' && (app.restoreAlert || defaultRestoreAlert) as restore" class="notifier-item" style="box-shadow: 0 0 5px 1px var(--ion-color-danger); margin-bottom: 40px">
|
||||
<ion-label class="ion-text-wrap">
|
||||
<h2 style="display: flex; align-items: center; margin-bottom: 3px;">
|
||||
<ion-icon style="margin-right: 5px;" slot="start" color="danger" slot="start" name="warning-outline"></ion-icon>
|
||||
<ion-text color="danger" style="font-size: medium; font-weight: bold">Warning</ion-text>
|
||||
</h2>
|
||||
<p style="font-size: small">{{restore}}</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item *ngIf="allPartitionsMounted">
|
||||
<ion-text *ngIf="type === 'create'" class="ion-text-wrap" color="warning">No partitions available. To begin a backup, insert a storage device into your Embassy.</ion-text>
|
||||
<ion-text *ngIf="type === 'restore'" class="ion-text-wrap" color="warning">No partitions available. Insert the storage device containing the backup you wish to restore.</ion-text>
|
||||
</ion-item>
|
||||
|
||||
<ion-item-group *ngFor="let d of disks">
|
||||
<ion-item-divider>{{ d.logicalname }} ({{ d.size }})</ion-item-divider>
|
||||
<ion-item-group>
|
||||
<ion-item button [disabled]="p.isMounted" *ngFor="let p of d.partitions" (click)="presentAlert(p)">
|
||||
<ion-item button [disabled]="p.isMounted" *ngFor="let p of d.partitions" (click)="presentAlert(d, p)">
|
||||
<ion-icon slot="start" name="save-outline"></ion-icon>
|
||||
<ion-label>
|
||||
<h2>{{ p.label || p.logicalname }}</h2>
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
.toast-close-button {
|
||||
color: var(--ion-color-primary) !important;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user