mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-27 02:41:53 +00:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
21f6560074 | ||
|
|
a077600c7e | ||
|
|
59f0d4e23a | ||
|
|
e64b92c5dd | ||
|
|
748379becc | ||
|
|
5b3163465d | ||
|
|
b00af8980a | ||
|
|
8708a4de8e | ||
|
|
d12a7f8931 | ||
|
|
8f9111ce3d | ||
|
|
7509c3a91e | ||
|
|
3126d6138e | ||
|
|
5d4837d942 | ||
|
|
660c0c5ff4 | ||
|
|
4c6c2768b3 | ||
|
|
6ddf7ce40b | ||
|
|
4f16d82294 | ||
|
|
7845044a3c | ||
|
|
20f91b10db | ||
|
|
ec2b707353 | ||
|
|
e609d3af1e | ||
|
|
5b5495cd51 | ||
|
|
8ba23f05a4 | ||
|
|
a4b1529dc4 |
@@ -90,9 +90,9 @@
|
||||
cat /proc/cpuinfo | grep Hardware
|
||||
```
|
||||
1. If your "Hardware" is [BCM2711](https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2711/README.md) then:
|
||||
1. Change `C compiler flags` to `-marm -mcpu=cortex-a72` in the GHC settings:
|
||||
1. Change `C compiler flags` to `-marm -fno-stack-protector -mcpu=cortex-a7` in the GHC settings:
|
||||
```
|
||||
nano ~/.stack/programs/arm-linux/ghc-8.10.2/lib/ghc-8.10.2/settings
|
||||
nano ~/.stack/programs/arm-linux/ghc-8.10.4/lib/ghc-8.10.4/settings
|
||||
```
|
||||
1. To prevent gcc errors we delete the `setup-exe-src` folder
|
||||
```
|
||||
@@ -145,7 +145,7 @@
|
||||
cd ~/embassy-os
|
||||
make
|
||||
|
||||
#Depending from your hadware this can take 1-2h+
|
||||
#Depending on your hardware this can take 1-2h+
|
||||
#Wait for the "DONE!" message and take note of your product_key
|
||||
exit
|
||||
```
|
||||
|
||||
18
README.md
18
README.md
@@ -13,10 +13,10 @@
|
||||
|
||||
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.
|
||||
|
||||
<img src="eos.png" width="100%">
|
||||
<img src="assets/eos.png" width="100%">
|
||||
|
||||
## :warning: 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.
|
||||
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 lose. Be #reckless at your own risk.
|
||||
|
||||
## Running EmbassyOS
|
||||
There are multiple ways to obtain and begin using EmbassyOS.
|
||||
@@ -24,7 +24,7 @@ There are multiple ways to obtain and begin using EmbassyOS.
|
||||
### :moneybag: 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.
|
||||
|
||||
### :hammer_and_wrench: Build your own Embassy
|
||||
### :construction_worker: 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.
|
||||
@@ -33,5 +33,15 @@ While not as convenient as buying an Embassy, this option is easier than you mig
|
||||
|
||||
To pursue this option, follow this [guide](https://docs.start9labs.com/getting-started/diy.html).
|
||||
|
||||
### :hammer_and_wrench: Build EmbassyOS from Source
|
||||
|
||||
EmbassyOS can be built from source, for personal use, for free.
|
||||
A detailed guide for doing so can be found [here](https://github.com/Start9Labs/embassy-os/blob/master/BuildGuide.md).
|
||||
|
||||
## :heart: 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).
|
||||
To contribute to the development of EmbassyOS, see [here](https://github.com/Start9Labs/embassy-os/blob/master/CONTRIBUTING.md).
|
||||
|
||||
## UI Screenshots
|
||||
<img src="assets/ServicesRunning.png" alt="Embassy Services" width="100%"> | <img src="assets/ServiceDetails.png" alt="Service Details" width="100%">
|
||||
--- | ---
|
||||
<img src="assets/Embassy.png" alt="EmbassyOS" width="100%"> | <img src="assets/Marketplace.png" alt="Marketplace" width="100%">
|
||||
|
||||
2
agent/.gitignore
vendored
2
agent/.gitignore
vendored
@@ -19,9 +19,7 @@ cabal.sandbox.config
|
||||
*.keter
|
||||
*~
|
||||
.vscode
|
||||
*.cabal
|
||||
\#*
|
||||
start9-companion-server.cabal
|
||||
stack.yaml.lock
|
||||
*.env
|
||||
agent_*
|
||||
|
||||
519
agent/ambassador-agent.cabal
Normal file
519
agent/ambassador-agent.cabal
Normal file
@@ -0,0 +1,519 @@
|
||||
cabal-version: 1.12
|
||||
|
||||
-- This file has been generated from package.yaml by hpack version 0.34.4.
|
||||
--
|
||||
-- see: https://github.com/sol/hpack
|
||||
|
||||
name: ambassador-agent
|
||||
version: 0.2.14
|
||||
build-type: Simple
|
||||
extra-source-files:
|
||||
./migrations/0.1.0::0.1.0
|
||||
./migrations/0.1.0::0.1.1
|
||||
./migrations/0.1.1::0.1.2
|
||||
./migrations/0.1.2::0.1.3
|
||||
./migrations/0.1.3::0.1.4
|
||||
./migrations/0.1.4::0.1.5
|
||||
./migrations/0.1.5::0.2.0
|
||||
./migrations/0.2.0::0.2.1
|
||||
./migrations/0.2.10::0.2.11
|
||||
./migrations/0.2.11::0.2.12
|
||||
./migrations/0.2.12::0.2.13
|
||||
./migrations/0.2.13::0.2.14
|
||||
./migrations/0.2.1::0.2.2
|
||||
./migrations/0.2.2::0.2.3
|
||||
./migrations/0.2.3::0.2.4
|
||||
./migrations/0.2.4::0.2.5
|
||||
./migrations/0.2.5::0.2.6
|
||||
./migrations/0.2.6::0.2.7
|
||||
./migrations/0.2.7::0.2.8
|
||||
./migrations/0.2.8::0.2.9
|
||||
./migrations/0.2.9::0.2.10
|
||||
|
||||
flag dev
|
||||
description: Turn on development settings, like auto-reload templates.
|
||||
manual: False
|
||||
default: False
|
||||
|
||||
flag disable-auth
|
||||
description: disable authorization checks
|
||||
manual: False
|
||||
default: False
|
||||
|
||||
flag library-only
|
||||
description: Build for use with "yesod devel"
|
||||
manual: False
|
||||
default: False
|
||||
|
||||
library
|
||||
exposed-modules:
|
||||
Application
|
||||
Auth
|
||||
Constants
|
||||
Daemon.AppNotifications
|
||||
Daemon.RefreshProcDev
|
||||
Daemon.SslRenew
|
||||
Daemon.TorHealth
|
||||
Daemon.ZeroConf
|
||||
Foundation
|
||||
Handler.Apps
|
||||
Handler.Authenticate
|
||||
Handler.Backups
|
||||
Handler.Hosts
|
||||
Handler.Icons
|
||||
Handler.Login
|
||||
Handler.Network
|
||||
Handler.Notifications
|
||||
Handler.PasswordUpdate
|
||||
Handler.PowerOff
|
||||
Handler.Register
|
||||
Handler.Register.Nginx
|
||||
Handler.Register.Tor
|
||||
Handler.SelfUpdate
|
||||
Handler.SshKeys
|
||||
Handler.Status
|
||||
Handler.Tor
|
||||
Handler.Types.Apps
|
||||
Handler.Types.HmacSig
|
||||
Handler.Types.Hosts
|
||||
Handler.Types.Metrics
|
||||
Handler.Types.Parse
|
||||
Handler.Types.Register
|
||||
Handler.Types.V0.Base
|
||||
Handler.Types.V0.Specs
|
||||
Handler.Types.V0.Ssh
|
||||
Handler.Types.V0.Wifi
|
||||
Handler.Util
|
||||
Handler.V0
|
||||
Handler.Wifi
|
||||
Lib.Algebra.Domain.AppMgr
|
||||
Lib.Algebra.Domain.AppMgr.TH
|
||||
Lib.Algebra.Domain.AppMgr.Types
|
||||
Lib.Algebra.State.RegistryUrl
|
||||
Lib.Avahi
|
||||
Lib.Background
|
||||
Lib.ClientManifest
|
||||
Lib.Crypto
|
||||
Lib.Database
|
||||
Lib.Error
|
||||
Lib.External.AppManifest
|
||||
Lib.External.AppMgr
|
||||
Lib.External.Metrics.Df
|
||||
Lib.External.Metrics.Iotop
|
||||
Lib.External.Metrics.ProcDev
|
||||
Lib.External.Metrics.Temperature
|
||||
Lib.External.Metrics.Top
|
||||
Lib.External.Metrics.Types
|
||||
Lib.External.Registry
|
||||
Lib.External.Specs.Common
|
||||
Lib.External.Specs.CPU
|
||||
Lib.External.Specs.Memory
|
||||
Lib.External.Util
|
||||
Lib.External.WpaSupplicant
|
||||
Lib.IconCache
|
||||
Lib.Metrics
|
||||
Lib.Migration
|
||||
Lib.Notifications
|
||||
Lib.Password
|
||||
Lib.ProductKey
|
||||
Lib.SelfUpdate
|
||||
Lib.Sound
|
||||
Lib.Ssh
|
||||
Lib.Ssl
|
||||
Lib.Synchronizers
|
||||
Lib.SystemCtl
|
||||
Lib.SystemPaths
|
||||
Lib.Tor
|
||||
Lib.TyFam.ConditionalData
|
||||
Lib.Types.Core
|
||||
Lib.Types.Emver
|
||||
Lib.Types.Emver.Orphans
|
||||
Lib.Types.NetAddress
|
||||
Lib.Types.ServerApp
|
||||
Lib.Types.Url
|
||||
Lib.WebServer
|
||||
Model
|
||||
Orphans.Digest
|
||||
Orphans.UUID
|
||||
Settings
|
||||
Startlude
|
||||
Startlude.ByteStream
|
||||
Startlude.ByteStream.Char8
|
||||
Util.Conduit
|
||||
Util.File
|
||||
Util.Function
|
||||
Util.Text
|
||||
other-modules:
|
||||
Paths_ambassador_agent
|
||||
hs-source-dirs:
|
||||
src
|
||||
default-extensions:
|
||||
NoImplicitPrelude
|
||||
BlockArguments
|
||||
ConstraintKinds
|
||||
DataKinds
|
||||
DeriveAnyClass
|
||||
DeriveFunctor
|
||||
DeriveGeneric
|
||||
DerivingStrategies
|
||||
EmptyCase
|
||||
FlexibleContexts
|
||||
FlexibleInstances
|
||||
GADTs
|
||||
GeneralizedNewtypeDeriving
|
||||
InstanceSigs
|
||||
KindSignatures
|
||||
LambdaCase
|
||||
MultiParamTypeClasses
|
||||
MultiWayIf
|
||||
NamedFieldPuns
|
||||
NumericUnderscores
|
||||
OverloadedStrings
|
||||
PolyKinds
|
||||
RankNTypes
|
||||
StandaloneDeriving
|
||||
StandaloneKindSignatures
|
||||
TupleSections
|
||||
TypeApplications
|
||||
TypeFamilies
|
||||
TypeOperators
|
||||
build-depends:
|
||||
aeson
|
||||
, aeson-flatten
|
||||
, attoparsec
|
||||
, base >=4.9.1.0 && <5
|
||||
, bytestring
|
||||
, casing
|
||||
, comonad
|
||||
, conduit
|
||||
, conduit-extra
|
||||
, connection
|
||||
, containers
|
||||
, cryptonite
|
||||
, cryptonite-conduit
|
||||
, data-default
|
||||
, directory
|
||||
, errors
|
||||
, exceptions
|
||||
, exinst
|
||||
, fast-logger
|
||||
, file-embed
|
||||
, filelock
|
||||
, filepath
|
||||
, fused-effects
|
||||
, fused-effects-th
|
||||
, git-embed
|
||||
, http-api-data
|
||||
, http-client
|
||||
, http-client-tls
|
||||
, http-conduit
|
||||
, http-types
|
||||
, interpolate
|
||||
, iso8601-time
|
||||
, json-rpc
|
||||
, lens
|
||||
, lens-aeson
|
||||
, lifted-async
|
||||
, lifted-base
|
||||
, memory
|
||||
, mime-types
|
||||
, monad-control
|
||||
, monad-logger
|
||||
, network
|
||||
, persistent
|
||||
, persistent-sqlite
|
||||
, persistent-template
|
||||
, process
|
||||
, process-extras
|
||||
, protolude
|
||||
, regex-compat
|
||||
, resourcet
|
||||
, shell-conduit
|
||||
, singletons
|
||||
, stm
|
||||
, streaming
|
||||
, streaming-bytestring
|
||||
, streaming-conduit
|
||||
, streaming-utils
|
||||
, tar-conduit
|
||||
, template-haskell
|
||||
, text >=0.11 && <2.0
|
||||
, time
|
||||
, transformers
|
||||
, transformers-base
|
||||
, typed-process
|
||||
, unix
|
||||
, unliftio
|
||||
, unliftio-core
|
||||
, unordered-containers
|
||||
, uuid
|
||||
, wai
|
||||
, wai-cors
|
||||
, wai-extra
|
||||
, warp
|
||||
, yaml
|
||||
, yesod
|
||||
, yesod-auth
|
||||
, yesod-core
|
||||
, yesod-form
|
||||
, yesod-persistent
|
||||
if (flag(dev)) || (flag(library-only))
|
||||
ghc-options: -Wall -Wunused-packages -fwarn-tabs -O0 -fdefer-typed-holes
|
||||
cpp-options: -DDEVELOPMENT
|
||||
else
|
||||
ghc-options: -Wall -Wunused-packages -fwarn-tabs -O2 -fdefer-typed-holes
|
||||
if (flag(disable-auth))
|
||||
cpp-options: -DDISABLE_AUTH
|
||||
default-language: Haskell2010
|
||||
|
||||
executable agent
|
||||
main-is: main.hs
|
||||
hs-source-dirs:
|
||||
app
|
||||
default-extensions:
|
||||
NoImplicitPrelude
|
||||
BlockArguments
|
||||
ConstraintKinds
|
||||
DataKinds
|
||||
DeriveAnyClass
|
||||
DeriveFunctor
|
||||
DeriveGeneric
|
||||
DerivingStrategies
|
||||
EmptyCase
|
||||
FlexibleContexts
|
||||
FlexibleInstances
|
||||
GADTs
|
||||
GeneralizedNewtypeDeriving
|
||||
InstanceSigs
|
||||
KindSignatures
|
||||
LambdaCase
|
||||
MultiParamTypeClasses
|
||||
MultiWayIf
|
||||
NamedFieldPuns
|
||||
NumericUnderscores
|
||||
OverloadedStrings
|
||||
PolyKinds
|
||||
RankNTypes
|
||||
StandaloneDeriving
|
||||
StandaloneKindSignatures
|
||||
TupleSections
|
||||
TypeApplications
|
||||
TypeFamilies
|
||||
TypeOperators
|
||||
ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N -fdefer-typed-holes
|
||||
build-depends:
|
||||
aeson
|
||||
, aeson-flatten
|
||||
, ambassador-agent
|
||||
, attoparsec
|
||||
, base >=4.9.1.0 && <5
|
||||
, bytestring
|
||||
, casing
|
||||
, comonad
|
||||
, conduit
|
||||
, conduit-extra
|
||||
, connection
|
||||
, containers
|
||||
, cryptonite
|
||||
, cryptonite-conduit
|
||||
, data-default
|
||||
, directory
|
||||
, errors
|
||||
, exceptions
|
||||
, exinst
|
||||
, fast-logger
|
||||
, file-embed
|
||||
, filelock
|
||||
, filepath
|
||||
, fused-effects
|
||||
, fused-effects-th
|
||||
, git-embed
|
||||
, http-api-data
|
||||
, http-client
|
||||
, http-client-tls
|
||||
, http-conduit
|
||||
, http-types
|
||||
, interpolate
|
||||
, iso8601-time
|
||||
, json-rpc
|
||||
, lens
|
||||
, lens-aeson
|
||||
, lifted-async
|
||||
, lifted-base
|
||||
, memory
|
||||
, mime-types
|
||||
, monad-control
|
||||
, monad-logger
|
||||
, network
|
||||
, persistent
|
||||
, persistent-sqlite
|
||||
, persistent-template
|
||||
, process
|
||||
, process-extras
|
||||
, protolude
|
||||
, regex-compat
|
||||
, resourcet
|
||||
, shell-conduit
|
||||
, singletons
|
||||
, stm
|
||||
, streaming
|
||||
, streaming-bytestring
|
||||
, streaming-conduit
|
||||
, streaming-utils
|
||||
, tar-conduit
|
||||
, template-haskell
|
||||
, text >=0.11 && <2.0
|
||||
, time
|
||||
, transformers
|
||||
, transformers-base
|
||||
, typed-process
|
||||
, unix
|
||||
, unliftio
|
||||
, unliftio-core
|
||||
, unordered-containers
|
||||
, uuid
|
||||
, wai
|
||||
, wai-cors
|
||||
, wai-extra
|
||||
, warp
|
||||
, yaml
|
||||
, yesod
|
||||
, yesod-auth
|
||||
, yesod-core
|
||||
, yesod-form
|
||||
, yesod-persistent
|
||||
if flag(library-only)
|
||||
buildable: False
|
||||
default-language: Haskell2010
|
||||
|
||||
test-suite agent-test
|
||||
type: exitcode-stdio-1.0
|
||||
main-is: Main.hs
|
||||
other-modules:
|
||||
ChecklistSpec
|
||||
Lib.External.AppManifestSpec
|
||||
Lib.SoundSpec
|
||||
Lib.Types.EmverProp
|
||||
Live.Metrics
|
||||
Live.Serialize
|
||||
Spec
|
||||
hs-source-dirs:
|
||||
test
|
||||
default-extensions:
|
||||
NoImplicitPrelude
|
||||
BlockArguments
|
||||
ConstraintKinds
|
||||
DataKinds
|
||||
DeriveAnyClass
|
||||
DeriveFunctor
|
||||
DeriveGeneric
|
||||
DerivingStrategies
|
||||
EmptyCase
|
||||
FlexibleContexts
|
||||
FlexibleInstances
|
||||
GADTs
|
||||
GeneralizedNewtypeDeriving
|
||||
InstanceSigs
|
||||
KindSignatures
|
||||
LambdaCase
|
||||
MultiParamTypeClasses
|
||||
MultiWayIf
|
||||
NamedFieldPuns
|
||||
NumericUnderscores
|
||||
OverloadedStrings
|
||||
PolyKinds
|
||||
RankNTypes
|
||||
StandaloneDeriving
|
||||
StandaloneKindSignatures
|
||||
TupleSections
|
||||
TypeApplications
|
||||
TypeFamilies
|
||||
TypeOperators
|
||||
ghc-options: -Wall -fdefer-typed-holes
|
||||
build-depends:
|
||||
aeson
|
||||
, aeson-flatten
|
||||
, ambassador-agent
|
||||
, attoparsec
|
||||
, base >=4.9.1.0 && <5
|
||||
, bytestring
|
||||
, casing
|
||||
, comonad
|
||||
, conduit
|
||||
, conduit-extra
|
||||
, connection
|
||||
, containers
|
||||
, cryptonite
|
||||
, cryptonite-conduit
|
||||
, data-default
|
||||
, directory
|
||||
, errors
|
||||
, exceptions
|
||||
, exinst
|
||||
, fast-logger
|
||||
, file-embed
|
||||
, filelock
|
||||
, filepath
|
||||
, fused-effects
|
||||
, fused-effects-th
|
||||
, git-embed
|
||||
, hedgehog
|
||||
, hspec >=2.0.0
|
||||
, hspec-expectations
|
||||
, http-api-data
|
||||
, http-client
|
||||
, http-client-tls
|
||||
, http-conduit
|
||||
, http-types
|
||||
, interpolate
|
||||
, iso8601-time
|
||||
, json-rpc
|
||||
, lens
|
||||
, lens-aeson
|
||||
, lifted-async
|
||||
, lifted-base
|
||||
, memory
|
||||
, mime-types
|
||||
, monad-control
|
||||
, monad-logger
|
||||
, network
|
||||
, persistent
|
||||
, persistent-sqlite
|
||||
, persistent-template
|
||||
, process
|
||||
, process-extras
|
||||
, protolude
|
||||
, random
|
||||
, regex-compat
|
||||
, resourcet
|
||||
, shell-conduit
|
||||
, singletons
|
||||
, stm
|
||||
, streaming
|
||||
, streaming-bytestring
|
||||
, streaming-conduit
|
||||
, streaming-utils
|
||||
, tar-conduit
|
||||
, template-haskell
|
||||
, text >=0.11 && <2.0
|
||||
, time
|
||||
, transformers
|
||||
, transformers-base
|
||||
, typed-process
|
||||
, unix
|
||||
, unliftio
|
||||
, unliftio-core
|
||||
, unordered-containers
|
||||
, uuid
|
||||
, wai
|
||||
, wai-cors
|
||||
, wai-extra
|
||||
, warp
|
||||
, yaml
|
||||
, yesod
|
||||
, yesod-auth
|
||||
, yesod-core
|
||||
, yesod-form
|
||||
, yesod-persistent
|
||||
, yesod-test
|
||||
default-language: Haskell2010
|
||||
23
agent/cabal.project
Normal file
23
agent/cabal.project
Normal file
@@ -0,0 +1,23 @@
|
||||
-- Generated by stackage-to-hackage
|
||||
|
||||
index-state: 2021-04-26T18:08:38Z
|
||||
|
||||
with-compiler: ghc-8.10.2
|
||||
|
||||
packages:
|
||||
./
|
||||
|
||||
source-repository-package
|
||||
type: git
|
||||
location: https://github.com/ProofOfKeags/persistent.git
|
||||
tag: 3b52b13d9ce79cdef14bb1c37cc527657a529462
|
||||
subdir: persistent-sqlite
|
||||
|
||||
allow-older: *
|
||||
allow-newer: *
|
||||
|
||||
package *
|
||||
ghc-options: -haddock
|
||||
|
||||
package ambassador-agent
|
||||
ghc-options: -fwrite-ide-info
|
||||
2513
agent/cabal.project.freeze
Normal file
2513
agent/cabal.project.freeze
Normal file
File diff suppressed because it is too large
Load Diff
@@ -33,5 +33,5 @@ database:
|
||||
database: "start9_agent.sqlite3"
|
||||
poolsize: "_env:YESOD_SQLITE_POOLSIZE:10"
|
||||
|
||||
app-mgr-version-spec: "=0.2.12"
|
||||
app-mgr-version-spec: "=0.2.14"
|
||||
#analytics: UA-YOURCODE
|
||||
|
||||
1
agent/migrations/0.2.12::0.2.13
Normal file
1
agent/migrations/0.2.12::0.2.13
Normal file
@@ -0,0 +1 @@
|
||||
SELECT TRUE;
|
||||
1
agent/migrations/0.2.13::0.2.14
Normal file
1
agent/migrations/0.2.13::0.2.14
Normal file
@@ -0,0 +1 @@
|
||||
SELECT TRUE;
|
||||
@@ -1,5 +1,5 @@
|
||||
name: ambassador-agent
|
||||
version: 0.2.12
|
||||
version: 0.2.14
|
||||
|
||||
default-extensions:
|
||||
- NoImplicitPrelude
|
||||
@@ -182,3 +182,4 @@ executables:
|
||||
condition: flag(library-only)
|
||||
- condition: false
|
||||
other-modules: Paths_ambassador_agent
|
||||
extra-source-files: ./migrations/*
|
||||
|
||||
@@ -154,8 +154,7 @@ getAvailableAppsLogic :: ( Has (Reader AgentCtx) sig m
|
||||
getAvailableAppsLogic = do
|
||||
jobCache <- asks appBackgroundJobs >>= liftIO . readTVarIO
|
||||
let installCache = inspect SInstalling jobCache
|
||||
(Reg.AppManifestRes apps, serverApps) <- LAsync.concurrently Reg.getAppManifest
|
||||
(AppMgr2.list [AppMgr2.flags|-s -d|])
|
||||
(Reg.AppIndexRes apps, serverApps) <- LAsync.concurrently Reg.getAppIndex (AppMgr2.list [AppMgr2.flags|-s -d|])
|
||||
let remapped = remapAppMgrInfo jobCache serverApps
|
||||
pure $ foreach apps $ \app@StoreApp { storeAppId } ->
|
||||
let installing =
|
||||
@@ -183,8 +182,9 @@ getAvailableAppByIdLogic appId = do
|
||||
let storeAppId' = storeAppId
|
||||
jobCache <- asks appBackgroundJobs >>= liftIO . readTVarIO
|
||||
let installCache = inspect SInstalling jobCache
|
||||
(Reg.AppManifestRes storeApps, serverApps) <- LAsync.concurrently Reg.getAppManifest
|
||||
(AppMgr2.list [AppMgr2.flags|-s -d|])
|
||||
((Reg.AppIndexRes storeApps, serverApps), AppManifest.AppManifest { appManifestLicenseName, appManifestLicenseLink }) <-
|
||||
LAsync.concurrently (LAsync.concurrently Reg.getAppIndex (AppMgr2.list [AppMgr2.flags|-s -d|]))
|
||||
(Reg.getAppManifest appId)
|
||||
StoreApp {..} <- pure (find ((== appId) . storeAppId) storeApps) `orThrowM` NotFoundE "appId" (show appId)
|
||||
let remapped = remapAppMgrInfo jobCache serverApps
|
||||
let installingInfo =
|
||||
@@ -213,6 +213,8 @@ getAvailableAppByIdLogic appId = do
|
||||
appId
|
||||
storeAppTitle
|
||||
(storeIconUrl appId (storeAppVersionInfoVersion $ extract storeAppVersions))
|
||||
, appAvailableFullLicenseName = appManifestLicenseName
|
||||
, appAvailableFullLicenseLink = appManifestLicenseLink
|
||||
, appAvailableFullInstallInfo = installingInfo
|
||||
, appAvailableFullVersionLatest = storeAppVersionInfoVersion latest
|
||||
, appAvailableFullDescriptionShort = storeAppDescriptionShort
|
||||
@@ -303,6 +305,8 @@ getInstalledAppByIdLogic appId = do
|
||||
backupTime <- lift $ LAsync.wait backupTime'
|
||||
hoistMaybe $ HM.lookup appId installCache <&> \(StoreApp {..}, StoreAppVersionInfo {..}) -> AppInstalledFull
|
||||
{ appInstalledFullBase = AppBase appId storeAppTitle (iconUrl appId storeAppVersionInfoVersion)
|
||||
, appInstalledFullLicenseName = Nothing
|
||||
, appInstalledFullLicenseLink = Nothing
|
||||
, appInstalledFullStatus = AppStatusTmp Installing
|
||||
, appInstalledFullVersionInstalled = storeAppVersionInfoVersion
|
||||
, appInstalledFullInstructions = Nothing
|
||||
@@ -319,7 +323,7 @@ getInstalledAppByIdLogic appId = do
|
||||
}
|
||||
serverApps <- AppMgr2.list [AppMgr2.flags|-s -d|]
|
||||
let remapped = remapAppMgrInfo jobCache serverApps
|
||||
appManifestFetchCached <- cached Reg.getAppManifest
|
||||
appManifestFetchCached <- cached Reg.getAppIndex
|
||||
let
|
||||
installed = do
|
||||
(status, version, AppMgr2.InfoRes {..}) <- hoistMaybe (HM.lookup appId remapped)
|
||||
@@ -333,7 +337,7 @@ getInstalledAppByIdLogic appId = do
|
||||
fromInstalled = (AppMgr2.infoResTitle &&& AppMgr2.infoResVersion)
|
||||
<$> hoistMaybe (HM.lookup depId serverApps)
|
||||
let fromStore = do
|
||||
Reg.AppManifestRes res <- lift appManifestFetchCached
|
||||
Reg.AppIndexRes res <- lift appManifestFetchCached
|
||||
(storeAppTitle &&& storeAppVersionInfoVersion . extract . storeAppVersions)
|
||||
<$> hoistMaybe (find ((== depId) . storeAppId) res)
|
||||
(title, v) <- fromInstalled <|> fromStore
|
||||
@@ -354,6 +358,8 @@ getInstalledAppByIdLogic appId = do
|
||||
guard (not . null $ lanConfs)
|
||||
pure $ LanAddress . (".onion" `Text.replace` ".local") . unTorAddress $ addrBase
|
||||
pure AppInstalledFull { appInstalledFullBase = AppBase appId infoResTitle (iconUrl appId version)
|
||||
, appInstalledFullLicenseName = AppManifest.appManifestLicenseName manifest
|
||||
, appInstalledFullLicenseLink = AppManifest.appManifestLicenseLink manifest
|
||||
, appInstalledFullStatus = status
|
||||
, appInstalledFullVersionInstalled = version
|
||||
, appInstalledFullInstructions = instructions
|
||||
@@ -674,8 +680,8 @@ getAvailableAppVersionInfoLogic :: ( Has (Reader AgentCtx) sig m
|
||||
-> VersionRange
|
||||
-> m AppVersionInfo
|
||||
getAvailableAppVersionInfoLogic appId appVersionSpec = do
|
||||
jobCache <- asks appBackgroundJobs >>= liftIO . readTVarIO
|
||||
Reg.AppManifestRes storeApps <- Reg.getAppManifest
|
||||
jobCache <- asks appBackgroundJobs >>= liftIO . readTVarIO
|
||||
Reg.AppIndexRes storeApps <- Reg.getAppIndex
|
||||
let titles =
|
||||
(storeAppTitle &&& storeAppVersionInfoVersion . extract . storeAppVersions) <$> indexBy storeAppId storeApps
|
||||
StoreApp {..} <- find ((== appId) . storeAppId) storeApps `orThrowPure` NotFoundE "appId" (show appId)
|
||||
|
||||
@@ -14,6 +14,13 @@ import Network.HTTP.Simple
|
||||
import System.FilePath.Posix
|
||||
import Yesod.Core
|
||||
|
||||
import Control.Carrier.Reader hiding ( asks )
|
||||
import Control.Concurrent.STM ( modifyTVar
|
||||
, readTVarIO
|
||||
)
|
||||
import Control.Effect.Labelled ( runLabelled )
|
||||
import Crypto.Hash.Conduit ( hashFile )
|
||||
import qualified Data.HashMap.Strict as HM
|
||||
import Foundation
|
||||
import Lib.Algebra.State.RegistryUrl
|
||||
import Lib.Error
|
||||
@@ -21,16 +28,9 @@ import qualified Lib.External.Registry as Reg
|
||||
import Lib.IconCache
|
||||
import Lib.SystemPaths hiding ( (</>) )
|
||||
import Lib.Types.Core
|
||||
import Lib.Types.Emver
|
||||
import Lib.Types.ServerApp
|
||||
import Settings
|
||||
import Control.Carrier.Reader hiding ( asks )
|
||||
import Control.Effect.Labelled ( runLabelled )
|
||||
import qualified Data.HashMap.Strict as HM
|
||||
import Control.Concurrent.STM ( modifyTVar
|
||||
, readTVarIO
|
||||
)
|
||||
import Crypto.Hash.Conduit ( hashFile )
|
||||
import Lib.Types.Emver
|
||||
|
||||
iconUrl :: AppId -> Version -> Text
|
||||
iconUrl appId version = (foldMap (T.cons '/') . fst . renderRoute . AppIconR $ appId) <> "?" <> show version
|
||||
@@ -63,7 +63,7 @@ getAppIconR appId = handleS9ErrT $ do
|
||||
lift $ respondSource (parseContentType path) $ CB.sourceFile path .| awaitForever sendChunkBS
|
||||
where
|
||||
fetchIcon = do
|
||||
url <- find ((== appId) . storeAppId) . Reg.storeApps <$> Reg.getAppManifest >>= \case
|
||||
url <- find ((== appId) . storeAppId) . Reg.storeApps <$> Reg.getAppIndex >>= \case
|
||||
Nothing -> throwError $ NotFoundE "icon" (show appId)
|
||||
Just x -> pure . toS $ storeAppIconUrl x
|
||||
bp <- getAbsoluteLocationFor iconBasePath
|
||||
@@ -84,7 +84,7 @@ getAvailableAppIconR :: AppId -> Handler TypedContent
|
||||
getAvailableAppIconR appId = handleS9ErrT $ do
|
||||
s <- getsYesod appSettings
|
||||
url <- do
|
||||
find ((== appId) . storeAppId) . Reg.storeApps <$> interp s Reg.getAppManifest >>= \case
|
||||
find ((== appId) . storeAppId) . Reg.storeApps <$> interp s Reg.getAppIndex >>= \case
|
||||
Nothing -> throwE $ NotFoundE "icon" (show appId)
|
||||
Just x -> pure . toS $ storeAppIconUrl x
|
||||
req <- case parseRequest url of
|
||||
|
||||
@@ -74,6 +74,8 @@ instance FromJSON InstallNewAppReq where
|
||||
|
||||
data AppAvailableFull = AppAvailableFull
|
||||
{ appAvailableFullBase :: AppBase
|
||||
, appAvailableFullLicenseName :: Maybe Text
|
||||
, appAvailableFullLicenseLink :: Maybe Text
|
||||
, appAvailableFullInstallInfo :: Maybe (Version, AppStatus)
|
||||
, appAvailableFullVersionLatest :: Version
|
||||
, appAvailableFullDescriptionShort :: Text
|
||||
@@ -88,7 +90,9 @@ instance ToJSON AppAvailableFull where
|
||||
toJSON AppAvailableFull {..} = mergeTo
|
||||
(toJSON appAvailableFullBase)
|
||||
(object
|
||||
[ "versionInstalled" .= fmap fst appAvailableFullInstallInfo
|
||||
[ "licenseName" .= appAvailableFullLicenseName
|
||||
, "licenseLink" .= appAvailableFullLicenseLink
|
||||
, "versionInstalled" .= fmap fst appAvailableFullInstallInfo
|
||||
, "status" .= fmap snd appAvailableFullInstallInfo
|
||||
, "versionLatest" .= appAvailableFullVersionLatest
|
||||
, "descriptionShort" .= appAvailableFullDescriptionShort
|
||||
@@ -131,6 +135,8 @@ instance ToJSON (AppDependencyRequirement Keep) where
|
||||
-- mute violations downstream of version for installing apps
|
||||
data AppInstalledFull = AppInstalledFull
|
||||
{ appInstalledFullBase :: AppBase
|
||||
, appInstalledFullLicenseName :: Maybe Text
|
||||
, appInstalledFullLicenseLink :: Maybe Text
|
||||
, appInstalledFullStatus :: AppStatus
|
||||
, appInstalledFullVersionInstalled :: Version
|
||||
, appInstalledFullTorAddress :: Maybe TorAddress
|
||||
@@ -156,6 +162,8 @@ instance ToJSON AppInstalledFull where
|
||||
, "lanUi" .= appInstalledFullLanUi
|
||||
, "id" .= appBaseId appInstalledFullBase
|
||||
, "title" .= appBaseTitle appInstalledFullBase
|
||||
, "licenseName" .= appInstalledFullLicenseName
|
||||
, "licenseLink" .= appInstalledFullLicenseLink
|
||||
, "iconURL" .= appBaseIconUrl appInstalledFullBase
|
||||
, "versionInstalled" .= appInstalledFullVersionInstalled
|
||||
, "status" .= appInstalledFullStatus
|
||||
|
||||
4
agent/src/Lib/External/AppManifest.hs
vendored
4
agent/src/Lib/External/AppManifest.hs
vendored
@@ -78,6 +78,8 @@ data AppManifest where
|
||||
AppManifest ::{ appManifestId :: AppId
|
||||
, appManifestVersion :: Version
|
||||
, appManifestTitle :: Text
|
||||
, appManifestLicenseName :: Maybe Text
|
||||
, appManifestLicenseLink :: Maybe Text
|
||||
, appManifestDescShort :: Text
|
||||
, appManifestDescLong :: Text
|
||||
, appManifestReleaseNotes :: Text
|
||||
@@ -109,6 +111,8 @@ instance FromJSON AppManifest where
|
||||
appManifestId <- o .: "id"
|
||||
appManifestVersion <- o .: "version"
|
||||
appManifestTitle <- o .: "title"
|
||||
appManifestLicenseName <- o .:? "license-info" >>= traverse (.: "license")
|
||||
appManifestLicenseLink <- o .:? "license-info" >>= traverse (.: "url")
|
||||
appManifestDescShort <- o .: "description" >>= (.: "short")
|
||||
appManifestDescLong <- o .: "description" >>= (.: "long")
|
||||
appManifestReleaseNotes <- o .: "release-notes"
|
||||
|
||||
27
agent/src/Lib/External/Registry.hs
vendored
27
agent/src/Lib/External/Registry.hs
vendored
@@ -13,8 +13,8 @@ import Startlude.ByteStream hiding ( count )
|
||||
|
||||
import Conduit
|
||||
import Control.Algebra
|
||||
import Control.Effect.Lift
|
||||
import Control.Effect.Error
|
||||
import Control.Effect.Lift
|
||||
import Control.Effect.Reader.Labelled
|
||||
import Control.Monad.Fail ( fail )
|
||||
import Control.Monad.Trans.Resource
|
||||
@@ -30,15 +30,17 @@ import System.Directory
|
||||
import System.Process
|
||||
|
||||
import Constants
|
||||
import qualified Data.Aeson.Types ( parseEither )
|
||||
import Data.Time.ISO8601 ( parseISO8601 )
|
||||
import Lib.Algebra.State.RegistryUrl
|
||||
import Lib.Error
|
||||
import Lib.External.AppManifest
|
||||
import Lib.SystemPaths
|
||||
import Lib.Types.Core
|
||||
import Lib.Types.Emver
|
||||
import Lib.Types.ServerApp
|
||||
import Data.Time.ISO8601 ( parseISO8601 )
|
||||
|
||||
newtype AppManifestRes = AppManifestRes
|
||||
newtype AppIndexRes = AppIndexRes
|
||||
{ storeApps :: [StoreApp] } deriving (Eq, Show)
|
||||
|
||||
newtype RegistryVersionForSpecRes = RegistryVersionForSpecRes
|
||||
@@ -85,8 +87,8 @@ getLifelineBinary avs = do
|
||||
liftIO $ runConduitRes $ httpSource request getResponseBody .| sinkFile (toS lifelineTarget)
|
||||
liftIO $ void $ readProcessWithExitCode "chmod" ["700", toS lifelineTarget] ""
|
||||
|
||||
getAppManifest :: (MonadIO m, Has (Error S9Error) sig m, Has RegistryUrl sig m) => m AppManifestRes
|
||||
getAppManifest = do
|
||||
getAppIndex :: (MonadIO m, Has (Error S9Error) sig m, Has RegistryUrl sig m) => m AppIndexRes
|
||||
getAppIndex = do
|
||||
manifestPath <- registryManifestUrl
|
||||
req <- liftIO $ fmap setUserAgent . parseRequestThrow $ toS manifestPath
|
||||
val <- (liftIO . try @SomeException) (httpBS req) >>= \case
|
||||
@@ -96,22 +98,29 @@ getAppManifest = do
|
||||
Left e -> throwError $ RegistryParseE manifestPath . toS $ e
|
||||
Right a -> pure a
|
||||
|
||||
getAppManifest :: (MonadIO m, Has (Error S9Error) sig m, Has RegistryUrl sig m) => AppId -> m AppManifest
|
||||
getAppManifest appId = do
|
||||
let path = "/apps/manifest/" <> unAppId appId
|
||||
v <- registryRequest path
|
||||
case Data.Aeson.Types.parseEither parseJSON v of
|
||||
Left e -> throwError $ RegistryParseE path . toS $ e
|
||||
Right a -> pure a
|
||||
|
||||
getStoreAppInfo :: (MonadIO m, Has RegistryUrl sig m, Has (Error S9Error) sig m) => AppId -> m (Maybe StoreApp)
|
||||
getStoreAppInfo name = find ((== name) . storeAppId) . storeApps <$> getAppManifest
|
||||
getStoreAppInfo name = find ((== name) . storeAppId) . storeApps <$> getAppIndex
|
||||
|
||||
parseBsManifest :: Has RegistryUrl sig m => ByteString -> m (Either String AppManifestRes)
|
||||
parseBsManifest :: Has RegistryUrl sig m => ByteString -> m (Either String AppIndexRes)
|
||||
parseBsManifest bs = do
|
||||
parseRegistryRes' <- parseRegistryRes
|
||||
pure $ parseEither parseRegistryRes' . fromJust . decodeThrow $ bs
|
||||
|
||||
parseRegistryRes :: Has RegistryUrl sig m => m (Value -> Parser AppManifestRes)
|
||||
parseRegistryRes :: Has RegistryUrl sig m => m (Value -> Parser AppIndexRes)
|
||||
parseRegistryRes = do
|
||||
parseAppData' <- parseAppData
|
||||
pure $ withObject "app registry response" $ \obj -> do
|
||||
let keyVals = HM.toList obj
|
||||
let mManifestApps = fmap (\(k, v) -> parseMaybe (parseAppData' (AppId k)) v) keyVals
|
||||
pure . AppManifestRes . catMaybes $ mManifestApps
|
||||
pure . AppIndexRes . catMaybes $ mManifestApps
|
||||
|
||||
registryUrl :: (Has RegistryUrl sig m) => m Text
|
||||
registryUrl = maybe "https://registry.start9labs.com:443" show <$> getRegistryUrl
|
||||
|
||||
@@ -24,6 +24,7 @@ import qualified Data.Conduit.Combinators as Conduit
|
||||
import Data.Conduit.Shell hiding ( arch
|
||||
, hostname
|
||||
, patch
|
||||
, split
|
||||
, stream
|
||||
)
|
||||
import qualified Data.Conduit.Tar as Conduit
|
||||
@@ -50,6 +51,9 @@ import Constants
|
||||
import Control.Effect.Error hiding ( run )
|
||||
import Control.Effect.Labelled ( runLabelled )
|
||||
import Daemon.ZeroConf ( getStart9AgentHostname )
|
||||
import Data.ByteString.Char8 ( split )
|
||||
import qualified Data.ByteString.Char8 as C8
|
||||
import Data.Conduit.List ( consume )
|
||||
import qualified Data.Text as T
|
||||
import Foundation
|
||||
import Handler.Network
|
||||
@@ -98,12 +102,12 @@ parseKernelVersion = do
|
||||
pure $ KernelVersion (Version (major', minor', patch', 0)) arch
|
||||
|
||||
synchronizer :: Synchronizer
|
||||
synchronizer = sync_0_2_12
|
||||
synchronizer = sync_0_2_14
|
||||
{-# INLINE synchronizer #-}
|
||||
|
||||
sync_0_2_12 :: Synchronizer
|
||||
sync_0_2_12 = Synchronizer
|
||||
"0.2.12"
|
||||
sync_0_2_14 :: Synchronizer
|
||||
sync_0_2_14 = Synchronizer
|
||||
"0.2.14"
|
||||
[ syncCreateAgentTmp
|
||||
, syncCreateSshDir
|
||||
, syncRemoveAvahiSystemdDependency
|
||||
@@ -585,19 +589,34 @@ syncRestarterService = SyncOp "Install Restarter Service" check migrate True
|
||||
liftIO $ callCommand "systemctl enable restarter.timer"
|
||||
|
||||
syncUpgradeTor :: SyncOp
|
||||
syncUpgradeTor = SyncOp "Install Tor 0.3.5.14-1" check migrate False
|
||||
syncUpgradeTor = SyncOp "Install Latest Tor" check migrate False
|
||||
where
|
||||
check =
|
||||
liftIO
|
||||
$ ( run (shell [i|dpkg -l|] $| shell [i|grep tor|] $| shell [i|grep 0.3.5.14-1|] $| conduit await)
|
||||
$> False
|
||||
)
|
||||
`catch` \(e :: ProcessException) -> case e of
|
||||
ProcessException _ (ExitFailure 1) -> pure True
|
||||
_ -> throwIO e
|
||||
check = run $ do
|
||||
shell "apt-get clean"
|
||||
shell "apt-get update"
|
||||
mTorVersion <- (shell "dpkg -s tor" $| shell "grep '^Version'" $| shell "cut -d ' ' -f2" $| conduit await)
|
||||
let torVersion = case mTorVersion of
|
||||
Nothing -> panic "invalid output from dpkg, can't read tor version"
|
||||
Just x -> x
|
||||
pure $ compareTorVersions torVersion "0.3.5.15-1" == LT
|
||||
migrate = liftIO . run $ do
|
||||
shell "apt-get update"
|
||||
shell "apt-get install -y tor=0.3.5.14-1"
|
||||
availVersions <-
|
||||
(shell "apt-cache madison tor" $| shell "cut -d '|' -f2" $| shell "xargs" $| conduit consume)
|
||||
latest <- case lastMay $ sortBy compareTorVersions availVersions of
|
||||
Nothing -> throwIO $ ErrorCall "No available versions of tor"
|
||||
Just x -> pure x
|
||||
shell $ "apt-get install -y tor=" <> if "0.3.5.15-1" `elem` availVersions
|
||||
then "0.3.5.15-1"
|
||||
else (C8.unpack latest)
|
||||
compareTorVersions :: ByteString -> ByteString -> Ordering
|
||||
compareTorVersions a b =
|
||||
let a' = (traverse (readMaybe @Int . decodeUtf8) . (split '.' <=< split '-') $ a)
|
||||
b' = (traverse (readMaybe @Int . decodeUtf8) . (split '.' <=< split '-') $ b)
|
||||
in case liftA2 compare a' b' of
|
||||
Nothing -> panic "invalid tor version string"
|
||||
Just x -> x
|
||||
|
||||
|
||||
syncDropCertificateUniqueness :: SyncOp
|
||||
syncDropCertificateUniqueness = SyncOp "Eliminate OpenSSL unique_subject=yes" check migrate False
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
-- {-# OPTIONS_GHC -fno-warn-unused-imports #-}
|
||||
module Startlude.ByteStream
|
||||
( module Startlude.ByteStream
|
||||
, module BS
|
||||
)
|
||||
where
|
||||
( module BS
|
||||
) where
|
||||
|
||||
import Data.ByteString.Streaming as BS
|
||||
import Streaming.ByteString as BS
|
||||
hiding ( ByteString )
|
||||
import Data.ByteString.Streaming as X
|
||||
( ByteString )
|
||||
|
||||
type ByteStream m = X.ByteString m
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
module Startlude.ByteStream.Char8
|
||||
( module X
|
||||
)
|
||||
where
|
||||
) where
|
||||
|
||||
import Data.ByteString.Streaming.Char8
|
||||
as X
|
||||
import Streaming.ByteString.Char8 as X
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
resolver: nightly-2020-09-29
|
||||
resolver: lts-17.10
|
||||
|
||||
packages:
|
||||
- .
|
||||
- .
|
||||
|
||||
extra-deps:
|
||||
- aeson-1.4.7.1
|
||||
# - aeson-1.4.7.1
|
||||
- aeson-flatten-0.1.0.2
|
||||
- exinst-0.8
|
||||
- fused-effects-1.1.0.0
|
||||
@@ -12,13 +12,14 @@ extra-deps:
|
||||
- git-embed-0.1.0
|
||||
- json-stream-0.4.2.4
|
||||
- protolude-0.3.0
|
||||
- streaming-bytestring-0.1.7
|
||||
- streaming-conduit-0.1.2.2
|
||||
- streaming-utils-0.2.0.0
|
||||
# to avoid the ridiculous bug where stat64 is not found (only affects development)
|
||||
- git: https://github.com/ProofOfKeags/persistent.git
|
||||
commit: 3b52b13d9ce79cdef14bb1c37cc527657a529462
|
||||
subdirs:
|
||||
- persistent-sqlite
|
||||
# - git: https://github.com/ProofOfKeags/persistent.git
|
||||
# commit: 3b52b13d9ce79cdef14bb1c37cc527657a529462
|
||||
# subdirs:
|
||||
# - persistent-sqlite
|
||||
|
||||
ghc-options:
|
||||
"$locals": -fwrite-ide-info
|
||||
|
||||
2
appmgr/Cargo.lock
generated
2
appmgr/Cargo.lock
generated
@@ -41,7 +41,7 @@ checksum = "afddf7f520a80dbf76e6f50a35bca42a2331ef227a28b3b6dc5c2e2338d114b1"
|
||||
|
||||
[[package]]
|
||||
name = "appmgr"
|
||||
version = "0.2.12"
|
||||
version = "0.2.14"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"avahi-sys",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
authors = ["Aiden McClelland <me@drbonez.dev>"]
|
||||
edition = "2018"
|
||||
name = "appmgr"
|
||||
version = "0.2.12"
|
||||
version = "0.2.14"
|
||||
|
||||
[lib]
|
||||
name = "appmgrlib"
|
||||
@@ -20,7 +20,9 @@ production = []
|
||||
|
||||
[dependencies]
|
||||
async-trait = "0.1.42"
|
||||
avahi-sys = { git = "https://github.com/Start9Labs/avahi-sys", branch = "feature/dynamic-linking", features = ["dynamic"], optional = true }
|
||||
avahi-sys = { git = "https://github.com/Start9Labs/avahi-sys", branch = "feature/dynamic-linking", features = [
|
||||
"dynamic",
|
||||
], optional = true }
|
||||
base32 = "0.4.0"
|
||||
clap = "2.33"
|
||||
ctrlc = "3.1.7"
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# appmgr
|
||||
|
||||
# Instructions
|
||||
|
||||
Clone the repo and enter the appmgr directory
|
||||
|
||||
`git clone https://github.com/Start9Labs/embassy-os.git`
|
||||
|
||||
`cd embassy-os/appmgr`
|
||||
|
||||
Install the portable version of appmgr
|
||||
|
||||
`cargo install --path=. --features=portable --no-default-features`
|
||||
|
||||
## Exit Codes
|
||||
1. General Error
|
||||
2. File System IO Error
|
||||
@@ -7,4 +19,4 @@
|
||||
4. Config Spec violation
|
||||
5. Config Rules violation
|
||||
6. Requested value does not exist
|
||||
7. Invalid Backup Password
|
||||
7. Invalid Backup Password
|
||||
|
||||
@@ -29,8 +29,10 @@ mod v0_2_9;
|
||||
mod v0_2_10;
|
||||
mod v0_2_11;
|
||||
mod v0_2_12;
|
||||
mod v0_2_13;
|
||||
mod v0_2_14;
|
||||
|
||||
pub use v0_2_12::Version as Current;
|
||||
pub use v0_2_14::Version as Current;
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[serde(untagged)]
|
||||
@@ -55,6 +57,8 @@ enum Version {
|
||||
V0_2_10(Wrapper<v0_2_10::Version>),
|
||||
V0_2_11(Wrapper<v0_2_11::Version>),
|
||||
V0_2_12(Wrapper<v0_2_12::Version>),
|
||||
V0_2_13(Wrapper<v0_2_13::Version>),
|
||||
V0_2_14(Wrapper<v0_2_14::Version>),
|
||||
Other(emver::Version),
|
||||
}
|
||||
|
||||
@@ -169,6 +173,8 @@ pub async fn init() -> Result<(), failure::Error> {
|
||||
Version::V0_2_10(v) => v.0.migrate_to(&Current::new()).await?,
|
||||
Version::V0_2_11(v) => v.0.migrate_to(&Current::new()).await?,
|
||||
Version::V0_2_12(v) => v.0.migrate_to(&Current::new()).await?,
|
||||
Version::V0_2_13(v) => v.0.migrate_to(&Current::new()).await?,
|
||||
Version::V0_2_14(v) => v.0.migrate_to(&Current::new()).await?,
|
||||
Version::Other(_) => (),
|
||||
// TODO find some way to automate this?
|
||||
}
|
||||
@@ -262,6 +268,8 @@ pub async fn self_update(requirement: emver::VersionRange) -> Result<(), Error>
|
||||
Version::V0_2_10(v) => Current::new().migrate_to(&v.0).await?,
|
||||
Version::V0_2_11(v) => Current::new().migrate_to(&v.0).await?,
|
||||
Version::V0_2_12(v) => Current::new().migrate_to(&v.0).await?,
|
||||
Version::V0_2_13(v) => Current::new().migrate_to(&v.0).await?,
|
||||
Version::V0_2_14(v) => Current::new().migrate_to(&v.0).await?,
|
||||
Version::Other(_) => (),
|
||||
// TODO find some way to automate this?
|
||||
};
|
||||
|
||||
21
appmgr/src/version/v0_2_13.rs
Normal file
21
appmgr/src/version/v0_2_13.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
use super::*;
|
||||
|
||||
const V0_2_13: emver::Version = emver::Version::new(0, 2, 13, 0);
|
||||
|
||||
pub struct Version;
|
||||
#[async_trait]
|
||||
impl VersionT for Version {
|
||||
type Previous = v0_2_12::Version;
|
||||
fn new() -> Self {
|
||||
Version
|
||||
}
|
||||
fn semver(&self) -> &'static emver::Version {
|
||||
&V0_2_13
|
||||
}
|
||||
async fn up(&self) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
async fn down(&self) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
21
appmgr/src/version/v0_2_14.rs
Normal file
21
appmgr/src/version/v0_2_14.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
use super::*;
|
||||
|
||||
const V0_2_14: emver::Version = emver::Version::new(0, 2, 14, 0);
|
||||
|
||||
pub struct Version;
|
||||
#[async_trait]
|
||||
impl VersionT for Version {
|
||||
type Previous = v0_2_13::Version;
|
||||
fn new() -> Self {
|
||||
Version
|
||||
}
|
||||
fn semver(&self) -> &'static emver::Version {
|
||||
&V0_2_14
|
||||
}
|
||||
async fn up(&self) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
async fn down(&self) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
BIN
assets/Embassy.png
Normal file
BIN
assets/Embassy.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 52 KiB |
BIN
assets/Marketplace.png
Normal file
BIN
assets/Marketplace.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 135 KiB |
BIN
assets/ServiceDetails.png
Normal file
BIN
assets/ServiceDetails.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 115 KiB |
BIN
assets/ServicesRunning.png
Normal file
BIN
assets/ServicesRunning.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 174 KiB |
BIN
assets/eos.png
Normal file
BIN
assets/eos.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.0 MiB |
41
ui/build-send-alpha.sh
Executable file
41
ui/build-send-alpha.sh
Executable file
@@ -0,0 +1,41 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "turn off mocks"
|
||||
echo "$( jq '.useMocks = false' use-mocks.json )" > use-mocks.json
|
||||
echo "$( jq '.skipStartupAlerts = false' use-mocks.json )" > use-mocks.json
|
||||
|
||||
echo "FILTER: rm -rf www"
|
||||
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: removing mock icons"
|
||||
rm -rf www/assets/img/service-icons
|
||||
|
||||
echo "FILTER: tar -zcvf ambassador-ui.tar.gz www"
|
||||
tar -zcvf ambassador-ui.tar.gz www
|
||||
|
||||
SHA_SUM=$(sha1sum ambassador-ui.tar.gz)
|
||||
echo "${SHA_SUM}"
|
||||
|
||||
echo "Set version"
|
||||
VERSION=$(jq ".version" package.json)
|
||||
echo "${VERSION}"
|
||||
|
||||
echo "FILTER: mkdir alpha-reg"
|
||||
ssh root@alpha-registry.start9labs.com "mkdir -p /var/www/html/resources/sys/ambassador-ui.tar.gz/${VERSION}"
|
||||
|
||||
echo "FILTER: scp ambassador-ui.tar.gz"
|
||||
scp ambassador-ui.tar.gz root@alpha-registry.start9labs.com:/var/www/html/resources/sys/ambassador-ui.tar.gz/${VERSION}/ambassador-ui.tar.gz
|
||||
|
||||
echo "FILTER: fin"
|
||||
@@ -1,6 +1,6 @@
|
||||
manifest-version: 0
|
||||
app-id: start9-ambassador
|
||||
app-version: 0.2.12
|
||||
app-version: 0.2.14
|
||||
uri-rewrites:
|
||||
- =/api -> http://{{start9-ambassador}}:5959/authenticate
|
||||
- /api/ -> http://{{start9-ambassador}}:5959/
|
||||
|
||||
2
ui/package-lock.json
generated
2
ui/package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "embassy-ui",
|
||||
"version": "0.2.12",
|
||||
"version": "0.2.14",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "embassy-ui",
|
||||
"version": "0.2.12",
|
||||
"version": "0.2.14",
|
||||
"description": "GUI for EmbassyOS",
|
||||
"author": "Start9 Labs",
|
||||
"homepage": "https://github.com/Start9Labs/embassy-ui",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title >
|
||||
<ion-label style="font-size: 20px;" class="ion-text-wrap">Welcome to 0.2.12!</ion-label>
|
||||
<ion-label style="font-size: 20px;" class="ion-text-wrap">Welcome to 0.2.14!</ion-label>
|
||||
</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
@@ -10,18 +10,7 @@
|
||||
<div style="display: flex; flex-direction: column; justify-content: space-between; height: 100%">
|
||||
<h2>Highlights</h2>
|
||||
<div class="main-content">
|
||||
<p>This release includes several bugfixes to resolve:</p>
|
||||
<ol>
|
||||
<li>Faster file upload/download ability</li>
|
||||
<li>More robust support for .local addresses</li>
|
||||
<li>Refreshing error messages during configuration changes</li>
|
||||
<li>Starting services with uninstalled optional dependencies</li>
|
||||
<li>Uninstalling services with optional dependencies</li>
|
||||
<li>Redirecting to HTTPS when navigating to LAN address</li>
|
||||
<li>Displaying warning messages during concurrent upgrades of dependent services</li>
|
||||
<li>Allowing larger file uploads</li>
|
||||
<li>Patching a security fix for Tor</li>
|
||||
</ol>
|
||||
<p>This release contains an important security patch to the tor binaries</p>
|
||||
</div>
|
||||
|
||||
<div class="close-button">
|
||||
|
||||
@@ -18,9 +18,11 @@ export interface AppAvailablePreview extends BaseApp {
|
||||
}
|
||||
|
||||
export type AppAvailableFull =
|
||||
AppAvailablePreview &
|
||||
{ descriptionLong: string
|
||||
AppAvailablePreview & {
|
||||
descriptionLong: string
|
||||
versions: string[]
|
||||
licenseName?: string // @TODO required for 0.3.0
|
||||
licenseLink?: string // @TODO required for 0.3.0
|
||||
} &
|
||||
AppAvailableVersionSpecificInfo
|
||||
|
||||
@@ -45,6 +47,8 @@ export interface AppInstalledPreview extends BaseApp {
|
||||
}
|
||||
|
||||
export interface AppInstalledFull extends AppInstalledPreview {
|
||||
licenseName?: string // @TODO required for 0.3.0
|
||||
licenseLink?: string // @TODO required for 0.3.0
|
||||
instructions: string | null
|
||||
lastBackup: string | null
|
||||
configuredRequirements: AppDependency[] | null // null if not yet configured
|
||||
|
||||
@@ -21,6 +21,26 @@
|
||||
<ion-text class="ion-text-wrap" color="danger">{{ error }}</ion-text>
|
||||
</ion-item>
|
||||
|
||||
<ion-card *ngIf="v1Status.status === 'instructions'" [href]="'https://start9.com/eos-' + v1Status.version" target="_blank" class="instructions-card">
|
||||
<ion-card-header>
|
||||
<ion-card-subtitle>Coming Soon...</ion-card-subtitle>
|
||||
<ion-card-title>EmbassyOS Version {{ v1Status.version }}</ion-card-title>
|
||||
</ion-card-header>
|
||||
<ion-card-content>
|
||||
<b>Get ready. View the update instructions.</b>
|
||||
</ion-card-content>
|
||||
</ion-card>
|
||||
|
||||
<ion-card *ngIf="v1Status.status === 'available'" [href]="'https://start9.com/eos-' + v1Status.version" target="_blank" class="available-card">
|
||||
<ion-card-header>
|
||||
<ion-card-subtitle>Now Available...</ion-card-subtitle>
|
||||
<ion-card-title>EmbassyOS Version {{ v1Status.version }}</ion-card-title>
|
||||
</ion-card-header>
|
||||
<ion-card-content>
|
||||
<b>View the update instructions.</b>
|
||||
</ion-card-content>
|
||||
</ion-card>
|
||||
|
||||
<ion-card *ngFor="let app of apps" style="margin: 10px 10px;" [routerLink]="[app.id]">
|
||||
<ion-item style="--inner-border-width: 0 0 .4px 0; --border-color: #525252;" *ngIf="{ installing: (app.subject.status | async) === 'INSTALLING', installComparison: app.subject | compareInstalledAndLatest | async } as l">
|
||||
<ion-avatar style="margin-top: 8px;" slot="start">
|
||||
|
||||
@@ -3,4 +3,14 @@
|
||||
font-style: italic;
|
||||
font-family: 'Open Sans';
|
||||
padding: 1px 0px 1.5px 0px;
|
||||
}
|
||||
|
||||
.instructions-card {
|
||||
--background: linear-gradient(45deg, #101010 16%, var(--ion-color-medium) 150%);
|
||||
margin: 16px 10px;
|
||||
}
|
||||
|
||||
.available-card {
|
||||
--background: linear-gradient(45deg, #101010 16%, var(--ion-color-danger) 150%);
|
||||
margin: 16px 10px;
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import { Subscription, BehaviorSubject, combineLatest } from 'rxjs'
|
||||
import { take } from 'rxjs/operators'
|
||||
import { markAsLoadingDuringP } from 'src/app/services/loader.service'
|
||||
import { OsUpdateService } from 'src/app/services/os-update.service'
|
||||
import { V1Status } from 'src/app/services/api/api-types'
|
||||
|
||||
@Component({
|
||||
selector: 'app-available-list',
|
||||
@@ -20,6 +21,7 @@ export class AppAvailableListPage {
|
||||
installedAppDeltaSubscription: Subscription
|
||||
apps: PropertySubjectId<AppAvailablePreview>[] = []
|
||||
appsInstalled: PropertySubjectId<AppInstalledPreview>[] = []
|
||||
v1Status: V1Status = { status: 'nothing', version: '' }
|
||||
|
||||
constructor (
|
||||
private readonly apiService: ApiService,
|
||||
@@ -35,6 +37,7 @@ export class AppAvailableListPage {
|
||||
|
||||
markAsLoadingDuringP(this.$loading$, Promise.all([
|
||||
this.getApps(),
|
||||
this.checkV1Status(),
|
||||
this.osUpdateService.checkWhenNotAvailable$().toPromise(), // checks for an os update, banner component renders conditionally
|
||||
pauseFor(600),
|
||||
]))
|
||||
@@ -44,6 +47,14 @@ export class AppAvailableListPage {
|
||||
this.appModel.getContents().forEach(appInstalled => this.mergeInstalledProps(appInstalled.id))
|
||||
}
|
||||
|
||||
async checkV1Status () {
|
||||
try {
|
||||
this.v1Status = await this.apiService.checkV1Status()
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
mergeInstalledProps (appInstalledId: string) {
|
||||
const appAvailable = this.apps.find(app => app.id === appInstalledId)
|
||||
if (!appAvailable) return
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
versionInstalled: $app$.versionInstalled | async,
|
||||
versionViewing: $app$.versionViewing | async,
|
||||
descriptionLong: $app$.descriptionLong | async,
|
||||
licenseName: $app$.licenseName | async,
|
||||
licenseLink: $app$.licenseLink | async,
|
||||
serviceRequirements: $app$.serviceRequirements | async,
|
||||
iconURL: $app$.iconURL | async,
|
||||
releaseNotes: $app$.releaseNotes | async
|
||||
@@ -112,9 +114,14 @@
|
||||
<dependency-list [$loading$]="$dependenciesLoading$" [hostApp]="$app$ | peekProperties" [dependencies]="vars.serviceRequirements"></dependency-list>
|
||||
</ng-container>
|
||||
<ion-item-divider></ion-item-divider>
|
||||
<ion-item *ngIf="vars.licenseLink" lines="none" button [href]="vars.licenseLink" target="_blank">
|
||||
<ion-icon slot="start" name="newspaper-outline"></ion-icon>
|
||||
<ion-label>License</ion-label>
|
||||
<ion-note slot="end">{{ vars.licenseName }}</ion-note>
|
||||
</ion-item>
|
||||
<ion-item lines="none" button (click)="presentAlertVersions()">
|
||||
<ion-icon color="medium" slot="start" name="file-tray-stacked-outline"></ion-icon>
|
||||
<ion-label color="medium">Other versions</ion-label>
|
||||
<ion-icon slot="start" name="file-tray-stacked-outline"></ion-icon>
|
||||
<ion-label>Other versions</ion-label>
|
||||
</ion-item>
|
||||
</ion-item-group>
|
||||
</ng-container>
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
torAddress: app.torAddress | async,
|
||||
status: app.status | async,
|
||||
versionInstalled: app.versionInstalled | async,
|
||||
licenseName: app.licenseName | async,
|
||||
licenseLink: app.licenseLink | async,
|
||||
configuredRequirements: app.configuredRequirements | async,
|
||||
lastBackup: app.lastBackup | async,
|
||||
hasFetchedFull: app.hasFetchedFull | async,
|
||||
@@ -157,6 +159,12 @@
|
||||
<ion-icon slot="start" name="storefront-outline" color="primary"></ion-icon>
|
||||
<ion-label><ion-text color="primary">Marketplace Listing</ion-text></ion-label>
|
||||
</ion-item>
|
||||
<!-- license -->
|
||||
<ion-item *ngIf="vars.licenseLink" lines="none" button [href]="vars.licenseLink" target="_blank">
|
||||
<ion-icon slot="start" name="newspaper-outline" color="primary"></ion-icon>
|
||||
<ion-label><ion-text color="primary">License</ion-text></ion-label>
|
||||
<ion-note slot="end">{{ vars.licenseName }}</ion-note>
|
||||
</ion-item>
|
||||
|
||||
<!-- dependencies -->
|
||||
<ng-container *ngIf="vars.configuredRequirements && vars.configuredRequirements.length">
|
||||
|
||||
@@ -37,3 +37,7 @@ export interface ApiAppConfig {
|
||||
|
||||
export type Unit = { never?: never; } // hack for the unit typ
|
||||
|
||||
export type V1Status = {
|
||||
status: 'nothing' | 'instructions' | 'available'
|
||||
version: string
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Rules } from '../../models/app-model'
|
||||
import { AppAvailablePreview, AppAvailableFull, AppInstalledPreview, AppInstalledFull, DependentBreakage, AppAvailableVersionSpecificInfo, ServiceAction } from '../../models/app-types'
|
||||
import { S9Notification, SSHFingerprint, ServerMetrics, DiskInfo } from '../../models/server-model'
|
||||
import { Subject, Observable } from 'rxjs'
|
||||
import { Unit, ApiServer, ApiAppInstalledFull, ApiAppConfig, ApiAppAvailableFull, ApiAppInstalledPreview } from './api-types'
|
||||
import { Unit, ApiServer, ApiAppInstalledFull, ApiAppConfig, ApiAppAvailableFull, ApiAppInstalledPreview, V1Status } from './api-types'
|
||||
import { AppMetrics, AppMetricsVersioned } from 'src/app/util/metrics.util'
|
||||
import { ConfigSpec } from 'src/app/app-config/config-types'
|
||||
|
||||
@@ -64,6 +64,7 @@ export abstract class ApiService {
|
||||
abstract ejectExternalDisk (logicalName: string): Promise<Unit>
|
||||
abstract serviceAction (appId: string, serviceAction: ServiceAction): Promise<ReqRes.ServiceActionResponse>
|
||||
abstract refreshLAN (): Promise<Unit>
|
||||
abstract checkV1Status (): Promise<V1Status>
|
||||
}
|
||||
|
||||
export function isRpcFailure<Error, Result> (arg: { error: Error } | { result: Result }): arg is { error: Error } {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { AppModel, AppStatus } from '../../models/app-model'
|
||||
import { AppAvailablePreview, AppAvailableFull, AppInstalledFull, AppInstalledPreview, DependentBreakage, AppAvailableVersionSpecificInfo, ServiceAction } from '../../models/app-types'
|
||||
import { S9Notification, SSHFingerprint, ServerModel, DiskInfo } from '../../models/server-model'
|
||||
import { ApiService, ReqRes } from './api.service'
|
||||
import { ApiAppInstalledPreview, ApiServer, Unit } from './api-types'
|
||||
import { ApiAppInstalledPreview, ApiServer, Unit, V1Status } from './api-types'
|
||||
import { HttpErrorResponse } from '@angular/common/http'
|
||||
import { isUnauthorized } from 'src/app/util/web.util'
|
||||
import { Replace } from 'src/app/util/types.util'
|
||||
@@ -17,7 +17,7 @@ import { ConfigService } from '../config.service'
|
||||
|
||||
@Injectable()
|
||||
export class LiveApiService extends ApiService {
|
||||
constructor(
|
||||
constructor (
|
||||
private readonly http: HttpService,
|
||||
// TODO remove app + server model from here. updates to state should be done in a separate class wrapping ApiService + App/ServerModel
|
||||
private readonly appModel: AppModel,
|
||||
@@ -25,40 +25,40 @@ export class LiveApiService extends ApiService {
|
||||
private readonly config: ConfigService,
|
||||
) { super() }
|
||||
|
||||
testConnection(url: string): Promise<true> {
|
||||
testConnection (url: string): Promise<true> {
|
||||
return this.http.raw.get(url).pipe(mapTo(true as true), catchError(e => catchHttpStatusError(e))).toPromise()
|
||||
}
|
||||
|
||||
// Used to check whether password or key is valid. If so, it will be used implicitly by all other calls.
|
||||
async getCheckAuth(): Promise<Unit> {
|
||||
async getCheckAuth (): Promise<Unit> {
|
||||
return this.http.serverRequest<Unit>({ method: Method.GET, url: '/authenticate' }, { version: '' })
|
||||
}
|
||||
|
||||
async postLogin(password: string): Promise<Unit> {
|
||||
async postLogin (password: string): Promise<Unit> {
|
||||
return this.http.serverRequest<Unit>({ method: Method.POST, url: '/auth/login', data: { password } }, { version: '' })
|
||||
}
|
||||
|
||||
async postLogout(): Promise<Unit> {
|
||||
return this.http.serverRequest<Unit>({ method: Method.POST, url: '/auth/logout' }, { version: '' }).then(() => { this.authenticatedRequestsEnabled = false; return {} })
|
||||
async postLogout (): Promise<Unit> {
|
||||
return this.http.serverRequest<Unit>({ method: Method.POST, url: '/auth/logout' }, { version: '' }).then(() => { this.authenticatedRequestsEnabled = false; return { } })
|
||||
}
|
||||
|
||||
async getServer(timeout?: number): Promise<ApiServer> {
|
||||
async getServer (timeout?: number): Promise<ApiServer> {
|
||||
return this.authRequest<ReqRes.GetServerRes>({ method: Method.GET, url: '/', readTimeout: timeout })
|
||||
}
|
||||
|
||||
async acknowledgeOSWelcome(version: string): Promise<Unit> {
|
||||
async acknowledgeOSWelcome (version: string): Promise<Unit> {
|
||||
return this.authRequest<Unit>({ method: Method.POST, url: `/welcome/${version}` })
|
||||
}
|
||||
|
||||
async getVersionLatest(): Promise<ReqRes.GetVersionLatestRes> {
|
||||
async getVersionLatest (): Promise<ReqRes.GetVersionLatestRes> {
|
||||
return this.authRequest<ReqRes.GetVersionLatestRes>({ method: Method.GET, url: '/versionLatest' }, { version: '' })
|
||||
}
|
||||
|
||||
async getServerMetrics(): Promise<ReqRes.GetServerMetricsRes> {
|
||||
async getServerMetrics (): Promise<ReqRes.GetServerMetricsRes> {
|
||||
return this.authRequest<ReqRes.GetServerMetricsRes>({ method: Method.GET, url: `/metrics` })
|
||||
}
|
||||
|
||||
async getNotifications(page: number, perPage: number): Promise<S9Notification[]> {
|
||||
async getNotifications (page: number, perPage: number): Promise<S9Notification[]> {
|
||||
const params: ReqRes.GetNotificationsReq = {
|
||||
page: String(page),
|
||||
perPage: String(perPage),
|
||||
@@ -66,27 +66,27 @@ export class LiveApiService extends ApiService {
|
||||
return this.authRequest<ReqRes.GetNotificationsRes>({ method: Method.GET, url: `/notifications`, params })
|
||||
}
|
||||
|
||||
async deleteNotification(id: string): Promise<Unit> {
|
||||
async deleteNotification (id: string): Promise<Unit> {
|
||||
return this.authRequest({ method: Method.DELETE, url: `/notifications/${id}` })
|
||||
}
|
||||
|
||||
async getExternalDisks(): Promise<DiskInfo[]> {
|
||||
async getExternalDisks (): Promise<DiskInfo[]> {
|
||||
return this.authRequest<ReqRes.GetExternalDisksRes>({ method: Method.GET, url: `/disks` })
|
||||
}
|
||||
|
||||
// TODO: EJECT-DISKS
|
||||
async ejectExternalDisk(logicalName: string): Promise<Unit> {
|
||||
async ejectExternalDisk (logicalName: string): Promise<Unit> {
|
||||
return this.authRequest({ method: Method.POST, url: `/disks/eject`, data: { logicalName } })
|
||||
}
|
||||
|
||||
async updateAgent(version: string): Promise<Unit> {
|
||||
async updateAgent (version: string): Promise<Unit> {
|
||||
const data: ReqRes.PostUpdateAgentReq = {
|
||||
version: `=${version}`,
|
||||
}
|
||||
return this.authRequest({ method: Method.POST, url: '/update', data })
|
||||
}
|
||||
|
||||
async getAvailableAppVersionSpecificInfo(appId: string, versionSpec: string): Promise<AppAvailableVersionSpecificInfo> {
|
||||
async getAvailableAppVersionSpecificInfo (appId: string, versionSpec: string): Promise<AppAvailableVersionSpecificInfo> {
|
||||
return this
|
||||
.authRequest<Replace<ReqRes.GetAppAvailableVersionInfoRes, 'versionViewing', 'version'>>({ method: Method.GET, url: `/apps/${appId}/store/${versionSpec}` })
|
||||
.then(res => ({ ...res, versionViewing: res.version }))
|
||||
@@ -96,7 +96,7 @@ export class LiveApiService extends ApiService {
|
||||
})
|
||||
}
|
||||
|
||||
async getAvailableApps(): Promise<AppAvailablePreview[]> {
|
||||
async getAvailableApps (): Promise<AppAvailablePreview[]> {
|
||||
const res = await this.authRequest<ReqRes.GetAppsAvailableRes>({ method: Method.GET, url: '/apps/store' })
|
||||
return res.map(a => {
|
||||
const latestVersionTimestamp = new Date(a.latestVersionTimestamp)
|
||||
@@ -105,7 +105,7 @@ export class LiveApiService extends ApiService {
|
||||
})
|
||||
}
|
||||
|
||||
async getAvailableApp(appId: string): Promise<AppAvailableFull> {
|
||||
async getAvailableApp (appId: string): Promise<AppAvailableFull> {
|
||||
return this.authRequest<ReqRes.GetAppAvailableRes>({ method: Method.GET, url: `/apps/${appId}/store` })
|
||||
.then(res => {
|
||||
return {
|
||||
@@ -115,7 +115,7 @@ export class LiveApiService extends ApiService {
|
||||
})
|
||||
}
|
||||
|
||||
async getInstalledApp(appId: string): Promise<AppInstalledFull> {
|
||||
async getInstalledApp (appId: string): Promise<AppInstalledFull> {
|
||||
return this.authRequest<ReqRes.GetAppInstalledRes>({ method: Method.GET, url: `/apps/${appId}/installed` })
|
||||
.then(app => {
|
||||
return {
|
||||
@@ -127,7 +127,7 @@ export class LiveApiService extends ApiService {
|
||||
})
|
||||
}
|
||||
|
||||
async getInstalledApps(): Promise<AppInstalledPreview[]> {
|
||||
async getInstalledApps (): Promise<AppInstalledPreview[]> {
|
||||
return this.authRequest<ReqRes.GetAppsInstalledRes>({ method: Method.GET, url: `/apps/installed` })
|
||||
.then(apps => {
|
||||
return apps.map(app => {
|
||||
@@ -140,24 +140,24 @@ export class LiveApiService extends ApiService {
|
||||
})
|
||||
}
|
||||
|
||||
async getAppConfig(appId: string): Promise<ReqRes.GetAppConfigRes> {
|
||||
async getAppConfig (appId: string): Promise<ReqRes.GetAppConfigRes> {
|
||||
return this.authRequest<ReqRes.GetAppConfigRes>({ method: Method.GET, url: `/apps/${appId}/config` })
|
||||
}
|
||||
|
||||
async getAppLogs(appId: string, params: ReqRes.GetAppLogsReq = {}): Promise<string[]> {
|
||||
async getAppLogs (appId: string, params: ReqRes.GetAppLogsReq = { }): Promise<string[]> {
|
||||
return this.authRequest<ReqRes.GetAppLogsRes>({ method: Method.GET, url: `/apps/${appId}/logs`, params: params as any })
|
||||
}
|
||||
|
||||
async getServerLogs(): Promise<string[]> {
|
||||
async getServerLogs (): Promise<string[]> {
|
||||
return this.authRequest<ReqRes.GetServerLogsRes>({ method: Method.GET, url: `/logs` })
|
||||
}
|
||||
|
||||
async getAppMetrics(appId: string): Promise<AppMetrics> {
|
||||
async getAppMetrics (appId: string): Promise<AppMetrics> {
|
||||
return this.authRequest<ReqRes.GetAppMetricsRes | string>({ method: Method.GET, url: `/apps/${appId}/metrics` })
|
||||
.then(parseMetricsPermissive)
|
||||
}
|
||||
|
||||
async installApp(appId: string, version: string, dryRun: boolean = false): Promise<AppInstalledFull & { breakages: DependentBreakage[] }> {
|
||||
async installApp (appId: string, version: string, dryRun: boolean = false): Promise<AppInstalledFull & { breakages: DependentBreakage[] }> {
|
||||
const data: ReqRes.PostInstallAppReq = {
|
||||
version,
|
||||
}
|
||||
@@ -172,94 +172,94 @@ export class LiveApiService extends ApiService {
|
||||
})
|
||||
}
|
||||
|
||||
async uninstallApp(appId: string, dryRun: boolean = false): Promise<{ breakages: DependentBreakage[] }> {
|
||||
async uninstallApp (appId: string, dryRun: boolean = false): Promise<{ breakages: DependentBreakage[] }> {
|
||||
return this.authRequest({ method: Method.POST, url: `/apps/${appId}/uninstall${dryRunParam(dryRun, true)}`, readTimeout: 60000 })
|
||||
}
|
||||
|
||||
async startApp(appId: string): Promise<Unit> {
|
||||
async startApp (appId: string): Promise<Unit> {
|
||||
return this.authRequest({ method: Method.POST, url: `/apps/${appId}/start`, readTimeout: 60000 })
|
||||
.then(() => this.appModel.update({ id: appId, status: AppStatus.RUNNING }))
|
||||
.then(() => ({}))
|
||||
.then(() => ({ }))
|
||||
}
|
||||
|
||||
async stopApp(appId: string, dryRun: boolean = false): Promise<{ breakages: DependentBreakage[] }> {
|
||||
async stopApp (appId: string, dryRun: boolean = false): Promise<{ breakages: DependentBreakage[] }> {
|
||||
const res = await this.authRequest<{ breakages: DependentBreakage[] }>({ method: Method.POST, url: `/apps/${appId}/stop${dryRunParam(dryRun, true)}`, readTimeout: 60000 })
|
||||
if (!dryRun) this.appModel.update({ id: appId, status: AppStatus.STOPPING }, modulateTime(new Date(), 5, 'seconds'))
|
||||
return res
|
||||
}
|
||||
|
||||
async restartApp(appId: string): Promise<Unit> {
|
||||
async restartApp (appId: string): Promise<Unit> {
|
||||
return this.authRequest({ method: Method.POST, url: `/apps/${appId}/restart`, readTimeout: 60000 })
|
||||
.then(() => ({} as any))
|
||||
.then(() => ({ } as any))
|
||||
}
|
||||
|
||||
async createAppBackup(appId: string, logicalname: string, password?: string): Promise<Unit> {
|
||||
async createAppBackup (appId: string, logicalname: string, password?: string): Promise<Unit> {
|
||||
const data: ReqRes.PostAppBackupCreateReq = {
|
||||
password: password || undefined,
|
||||
logicalname,
|
||||
}
|
||||
return this.authRequest<ReqRes.PostAppBackupCreateRes>({ method: Method.POST, url: `/apps/${appId}/backup`, data, readTimeout: 60000 })
|
||||
.then(() => this.appModel.update({ id: appId, status: AppStatus.CREATING_BACKUP }))
|
||||
.then(() => ({}))
|
||||
.then(() => ({ }))
|
||||
}
|
||||
|
||||
async stopAppBackup(appId: string): Promise<Unit> {
|
||||
async stopAppBackup (appId: string): Promise<Unit> {
|
||||
return this.authRequest<ReqRes.PostAppBackupStopRes>({ method: Method.POST, url: `/apps/${appId}/backup/stop`, readTimeout: 60000 })
|
||||
.then(() => this.appModel.update({ id: appId, status: AppStatus.STOPPED }))
|
||||
.then(() => ({}))
|
||||
.then(() => ({ }))
|
||||
}
|
||||
|
||||
async restoreAppBackup(appId: string, logicalname: string, password?: string): Promise<Unit> {
|
||||
async restoreAppBackup (appId: string, logicalname: string, password?: string): Promise<Unit> {
|
||||
const data: ReqRes.PostAppBackupRestoreReq = {
|
||||
password: password || undefined,
|
||||
logicalname,
|
||||
}
|
||||
return this.authRequest<ReqRes.PostAppBackupRestoreRes>({ method: Method.POST, url: `/apps/${appId}/backup/restore`, data, readTimeout: 60000 })
|
||||
.then(() => this.appModel.update({ id: appId, status: AppStatus.RESTORING_BACKUP }))
|
||||
.then(() => ({}))
|
||||
.then(() => ({ }))
|
||||
}
|
||||
|
||||
async patchAppConfig(app: AppInstalledPreview, config: object, dryRun = false): Promise<{ breakages: DependentBreakage[] }> {
|
||||
async patchAppConfig (app: AppInstalledPreview, config: object, dryRun = false): Promise<{ breakages: DependentBreakage[] }> {
|
||||
const data: ReqRes.PatchAppConfigReq = {
|
||||
config,
|
||||
}
|
||||
return this.authRequest({ method: Method.PATCH, url: `/apps/${app.id}/config${dryRunParam(dryRun, true)}`, data, readTimeout: 60000 })
|
||||
}
|
||||
|
||||
async postConfigureDependency(dependencyId: string, dependentId: string, dryRun?: boolean): Promise<{ config: object, breakages: DependentBreakage[] }> {
|
||||
async postConfigureDependency (dependencyId: string, dependentId: string, dryRun?: boolean): Promise<{ config: object, breakages: DependentBreakage[] }> {
|
||||
return this.authRequest({ method: Method.POST, url: `/apps/${dependencyId}/autoconfig/${dependentId}${dryRunParam(dryRun, true)}`, readTimeout: 60000 })
|
||||
}
|
||||
|
||||
async patchServerConfig(attr: string, value: any): Promise<Unit> {
|
||||
async patchServerConfig (attr: string, value: any): Promise<Unit> {
|
||||
const data: ReqRes.PatchServerConfigReq = {
|
||||
value,
|
||||
}
|
||||
return this.authRequest({ method: Method.PATCH, url: `/${attr}`, data, readTimeout: 60000 })
|
||||
.then(() => this.serverModel.update({ [attr]: value }))
|
||||
.then(() => ({}))
|
||||
.then(() => ({ }))
|
||||
}
|
||||
|
||||
async wipeAppData(app: AppInstalledPreview): Promise<Unit> {
|
||||
async wipeAppData (app: AppInstalledPreview): Promise<Unit> {
|
||||
return this.authRequest({ method: Method.POST, url: `/apps/${app.id}/wipe`, readTimeout: 60000 }).then((res) => {
|
||||
this.appModel.update({ id: app.id, status: AppStatus.NEEDS_CONFIG })
|
||||
return res
|
||||
})
|
||||
}
|
||||
|
||||
async toggleAppLAN(appId: string, toggle: 'enable' | 'disable'): Promise<Unit> {
|
||||
async toggleAppLAN (appId: string, toggle: 'enable' | 'disable'): Promise<Unit> {
|
||||
return this.authRequest({ method: Method.POST, url: `/apps/${appId}/lan/${toggle}` })
|
||||
}
|
||||
|
||||
async addSSHKey(sshKey: string): Promise<Unit> {
|
||||
async addSSHKey (sshKey: string): Promise<Unit> {
|
||||
const data: ReqRes.PostAddSSHKeyReq = {
|
||||
sshKey,
|
||||
}
|
||||
const fingerprint = await this.authRequest<ReqRes.PostAddSSHKeyRes>({ method: Method.POST, url: `/sshKeys`, data })
|
||||
this.serverModel.update({ ssh: [...this.serverModel.peek().ssh, fingerprint] })
|
||||
return {}
|
||||
return { }
|
||||
}
|
||||
|
||||
async addWifi(ssid: string, password: string, country: string, connect: boolean): Promise<Unit> {
|
||||
async addWifi (ssid: string, password: string, country: string, connect: boolean): Promise<Unit> {
|
||||
const data: ReqRes.PostAddWifiReq = {
|
||||
ssid,
|
||||
password,
|
||||
@@ -269,30 +269,30 @@ export class LiveApiService extends ApiService {
|
||||
return this.authRequest({ method: Method.POST, url: `/wifi`, data })
|
||||
}
|
||||
|
||||
async connectWifi(ssid: string): Promise<Unit> {
|
||||
async connectWifi (ssid: string): Promise<Unit> {
|
||||
return this.authRequest({ method: Method.POST, url: encodeURI(`/wifi/${ssid}`) })
|
||||
}
|
||||
|
||||
async deleteWifi(ssid: string): Promise<Unit> {
|
||||
async deleteWifi (ssid: string): Promise<Unit> {
|
||||
return this.authRequest({ method: Method.DELETE, url: encodeURI(`/wifi/${ssid}`) })
|
||||
}
|
||||
|
||||
async deleteSSHKey(fingerprint: SSHFingerprint): Promise<Unit> {
|
||||
async deleteSSHKey (fingerprint: SSHFingerprint): Promise<Unit> {
|
||||
await this.authRequest({ method: Method.DELETE, url: `/sshKeys/${fingerprint.hash}` })
|
||||
const ssh = this.serverModel.peek().ssh
|
||||
this.serverModel.update({ ssh: ssh.filter(s => s !== fingerprint) })
|
||||
return {}
|
||||
return { }
|
||||
}
|
||||
|
||||
async restartServer(): Promise<Unit> {
|
||||
async restartServer (): Promise<Unit> {
|
||||
return this.authRequest({ method: Method.POST, url: '/restart', readTimeout: 60000 })
|
||||
}
|
||||
|
||||
async shutdownServer(): Promise<Unit> {
|
||||
async shutdownServer (): Promise<Unit> {
|
||||
return this.authRequest({ method: Method.POST, url: '/shutdown', readTimeout: 60000 })
|
||||
}
|
||||
|
||||
async serviceAction(appId: string, s: ServiceAction): Promise<ReqRes.ServiceActionResponse> {
|
||||
async serviceAction (appId: string, s: ServiceAction): Promise<ReqRes.ServiceActionResponse> {
|
||||
const data: ReqRes.ServiceActionRequest = {
|
||||
jsonrpc: '2.0',
|
||||
id: uuid.v4(),
|
||||
@@ -301,11 +301,15 @@ export class LiveApiService extends ApiService {
|
||||
return this.authRequest({ method: Method.POST, url: `/apps/${appId}/actions`, data, readTimeout: 300000 })
|
||||
}
|
||||
|
||||
async refreshLAN(): Promise<Unit> {
|
||||
async refreshLAN (): Promise<Unit> {
|
||||
return this.authRequest({ method: Method.POST, url: '/network/lan/reset' })
|
||||
}
|
||||
|
||||
private async authRequest<T>(opts: HttpOptions, overrides: Partial<{ version: string }> = {}): Promise<T> {
|
||||
async checkV1Status (): Promise<V1Status> {
|
||||
return this.http.request({ method: Method.GET, url: 'https://registry.start9labs.com/sys/status' })
|
||||
}
|
||||
|
||||
private async authRequest<T> (opts: HttpOptions, overrides: Partial<{ version: string }> = { }): Promise<T> {
|
||||
if (!this.authenticatedRequestsEnabled) throw new Error(`Authenticated requests are not enabled. Do you need to login?`)
|
||||
|
||||
opts.withCredentials = true
|
||||
@@ -324,7 +328,7 @@ const dryRunParam = (dryRun: boolean, first: boolean) => {
|
||||
return first ? `?dryrun` : `&dryrun`
|
||||
}
|
||||
|
||||
function catchHttpStatusError(error: HttpErrorResponse): Observable<true> {
|
||||
function catchHttpStatusError (error: HttpErrorResponse): Observable<true> {
|
||||
if (error.error instanceof ErrorEvent) {
|
||||
// A client-side or network error occurred. Handle it accordingly.
|
||||
return throwError('Not Connected')
|
||||
|
||||
@@ -4,7 +4,7 @@ import { AppAvailablePreview, AppAvailableFull, AppInstalledPreview, AppInstalle
|
||||
import { S9Notification, SSHFingerprint, ServerStatus, ServerModel, DiskInfo } from '../../models/server-model'
|
||||
import { pauseFor } from '../../util/misc.util'
|
||||
import { ApiService, ReqRes } from './api.service'
|
||||
import { ApiAppInstalledFull, ApiAppInstalledPreview, ApiServer, Unit as EmptyResponse, Unit } from './api-types'
|
||||
import { ApiAppInstalledFull, ApiAppInstalledPreview, ApiServer, Unit as EmptyResponse, Unit, V1Status } from './api-types'
|
||||
import { AppMetrics, AppMetricsVersioned, parseMetricsPermissive } from 'src/app/util/metrics.util'
|
||||
import { mockApiAppAvailableFull, mockApiAppAvailableVersionInfo, mockApiAppInstalledFull, mockAppDependentBreakages, toInstalledPreview } from './mock-app-fixures'
|
||||
import { ConfigService } from '../config.service'
|
||||
@@ -273,12 +273,19 @@ export class MockApiService extends ApiService {
|
||||
return mockRefreshLAN()
|
||||
}
|
||||
|
||||
async checkV1Status (): Promise<V1Status> {
|
||||
return {
|
||||
status: 'instructions',
|
||||
version: '1.0.0',
|
||||
}
|
||||
}
|
||||
|
||||
private hasUI (app: ApiAppInstalledPreview): boolean {
|
||||
return app.lanUi || app.torUi
|
||||
}
|
||||
|
||||
private isLaunchable (app: ApiAppInstalledPreview): boolean {
|
||||
return !this.config.isConsulate &&
|
||||
return !this.config.isConsulate &&
|
||||
app.status === AppStatus.RUNNING &&
|
||||
(
|
||||
(app.torAddress && app.torUi && this.config.isTor()) ||
|
||||
@@ -355,7 +362,7 @@ async function mockGetServerLogs (): Promise<ReqRes.GetServerLogsRes> {
|
||||
|
||||
async function mockGetAppMetrics (): Promise<ReqRes.GetAppMetricsRes> {
|
||||
await pauseFor(1000)
|
||||
return mockApiAppMetricsV1
|
||||
return mockApiAppMetricsV1 as ReqRes.GetAppMetricsRes
|
||||
}
|
||||
|
||||
async function mockGetAvailableAppVersionInfo (): Promise<ReqRes.GetAppAvailableVersionInfoRes> {
|
||||
@@ -492,8 +499,8 @@ const mockApiNotifications: ReqRes.GetNotificationsRes = [
|
||||
const mockApiServer: () => ReqRes.GetServerRes = () => ({
|
||||
serverId: 'start9-mockxyzab',
|
||||
name: 'Embassy:12345678',
|
||||
versionInstalled: '0.2.12',
|
||||
versionLatest: '0.2.13',
|
||||
versionInstalled: '0.2.14',
|
||||
versionLatest: '0.2.14',
|
||||
status: ServerStatus.RUNNING,
|
||||
alternativeRegistryUrl: 'beta-registry.start9labs.com',
|
||||
welcomeAck: true,
|
||||
|
||||
@@ -54,6 +54,8 @@ export const bitcoinI: ApiAppInstalledFull = {
|
||||
versionInstalled: '0.18.1',
|
||||
lanAddress: undefined,
|
||||
title: 'Bitcoin Core',
|
||||
licenseName: 'MIT',
|
||||
licenseLink: 'https://github.com/bitcoin/bitcoin/blob/master/COPYING',
|
||||
torAddress: '4acth47i6kxnvkewtm6q7ib2s3ufpo5sqbsnzjpbi7utijcltosqemad.onion',
|
||||
startAlert: 'Bitcoind could take a loooooong time to start. Please be patient.',
|
||||
status: AppStatus.STOPPED,
|
||||
@@ -147,6 +149,8 @@ export const bitcoinA: AppAvailableFull = {
|
||||
id: 'bitcoind',
|
||||
versionLatest: '0.19.1.1',
|
||||
versionInstalled: '0.19.0',
|
||||
licenseName: 'MIT',
|
||||
licenseLink: 'https://github.com/bitcoin/bitcoin/blob/master/COPYING',
|
||||
status: AppStatus.UNKNOWN,
|
||||
title: 'Bitcoin Core',
|
||||
descriptionShort: 'Bitcoin is an innovative payment network and new kind of money.',
|
||||
|
||||
@@ -5,6 +5,7 @@ import { WizardBaker } from '../components/install-wizard/prebaked-wizards'
|
||||
import { OSWelcomePage } from '../modals/os-welcome/os-welcome.page'
|
||||
import { S9Server } from '../models/server-model'
|
||||
import { displayEmver } from '../pipes/emver.pipe'
|
||||
import { V1Status } from './api/api-types'
|
||||
import { ApiService, ReqRes } from './api/api.service'
|
||||
import { ConfigService } from './config.service'
|
||||
import { Emver } from './emver.service'
|
||||
@@ -36,6 +37,13 @@ export class StartupAlertsNotifier {
|
||||
display: vl => this.displayOsUpdateCheck(vl),
|
||||
hasRun: this.config.skipStartupAlerts,
|
||||
}
|
||||
const v1StatusUpdate: Check<V1Status> = {
|
||||
name: 'v1Status',
|
||||
shouldRun: s => this.shouldRunOsUpdateCheck(s),
|
||||
check: () => this.v1StatusCheck(),
|
||||
display: s => this.displayV1Check(s),
|
||||
hasRun: this.config.skipStartupAlerts,
|
||||
}
|
||||
const apps: Check<boolean> = {
|
||||
name: 'apps',
|
||||
shouldRun: s => this.shouldRunAppsCheck(s),
|
||||
@@ -43,7 +51,7 @@ export class StartupAlertsNotifier {
|
||||
display: () => this.displayAppsCheck(),
|
||||
hasRun: this.config.skipStartupAlerts,
|
||||
}
|
||||
this.checks = [welcome, osUpdate, apps]
|
||||
this.checks = [welcome, osUpdate, v1StatusUpdate, apps]
|
||||
}
|
||||
|
||||
// This takes our three checks and filters down to those that should run.
|
||||
@@ -85,6 +93,10 @@ export class StartupAlertsNotifier {
|
||||
return server.autoCheckUpdates
|
||||
}
|
||||
|
||||
private async v1StatusCheck (): Promise<V1Status> {
|
||||
return this.apiService.checkV1Status()
|
||||
}
|
||||
|
||||
private async osUpdateCheck (s: Readonly<S9Server>): Promise<ReqRes.GetVersionLatestRes | undefined> {
|
||||
const res = await this.apiService.getVersionLatest()
|
||||
return this.osUpdateService.updateIsAvailable(s.versionInstalled, res) ? res : undefined
|
||||
@@ -131,6 +143,33 @@ export class StartupAlertsNotifier {
|
||||
return true
|
||||
}
|
||||
|
||||
private async displayV1Check (s: V1Status): Promise<boolean> {
|
||||
return new Promise(async resolve => {
|
||||
if (s.status !== 'available') return resolve(true)
|
||||
const alert = await this.alertCtrl.create({
|
||||
backdropDismiss: true,
|
||||
header: `EmbassyOS ${s.version} Now Available!`,
|
||||
message: `Version ${s.version} introduces SSD support and a whole lot more.`,
|
||||
buttons: [
|
||||
{
|
||||
text: 'Cancel',
|
||||
role: 'cancel',
|
||||
handler: () => resolve(true),
|
||||
},
|
||||
{
|
||||
text: 'View Instructions',
|
||||
handler: () => {
|
||||
window.open(`https://start9.com/eos-${s.version}`, '_blank')
|
||||
resolve(false)
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
await alert.present()
|
||||
})
|
||||
}
|
||||
|
||||
private async displayAppsCheck (): Promise<boolean> {
|
||||
return new Promise(async resolve => {
|
||||
const alert = await this.alertCtrl.create({
|
||||
|
||||
Reference in New Issue
Block a user