Compare commits

...

14 Commits

Author SHA1 Message Date
Rokas Ambrazevicius f194fb9cca Add appearance setting 2024-02-01 22:34:37 +02:00
Rokas Ambrazevicius 36a4f93127 Add settings 2024-02-01 17:04:49 +02:00
Rokas Ambrazevicius aae5adef38 Add app icon 2024-01-18 14:51:25 +02:00
Rokas Ambrazevicius 51754f737c Add country selection view 2024-01-17 16:45:13 +02:00
Rokas Ambrazevicius 85ccd835ab Add hop list view 2024-01-12 16:36:16 +02:00
Rokas Ambrazevicius 8e302bfdde Add Settings package 2024-01-08 14:06:08 +02:00
Rokas Ambrazevicius b609331b8e Add Home View 2024-01-08 13:39:07 +02:00
Rokas Ambrazevicius 59845174e7 Add localizations and status button 2023-12-28 16:40:02 +02:00
Rokas Ambrazevicius 0bdb6a626d Setup packages 2023-12-27 16:13:20 +02:00
Rokas Ambrazevicius 7f0256cc33 Setup iOS project 2023-12-22 13:17:21 +02:00
mx 0d52800569 dkg ceremony overview (#4269)
* added initial dkg ceremony info

* rebase with pwnfoo's changes from #4270 

* final tweaks

---------

Co-authored-by: mfahampshire <mfahampshire@pm.me>
2023-12-21 16:55:58 +00:00
Sachin Kamath a1dd9e656d Add info on running full node & general improvements (#4270)
* documentation: add info on nyx full node

* documentation: https info for nym-api
2023-12-21 13:29:03 +00:00
Zane Schepke 46d68e5448 Merge pull request #4268 from nymtech/refactor/vpn_desktop_minor
refactor(vpn-desktop): minor changes
2023-12-20 12:09:56 -05:00
pierre 2c820ca0ea update country list, remove app name from topbar 2023-12-20 17:40:53 +01:00
159 changed files with 4861 additions and 77 deletions
@@ -25,14 +25,14 @@ These steps are for an operator who is setting up a [Gateway](gateway-setup.md)
1. New flags will need to be added to the `init` and `run` command. The `--host` option should be replaced with these flags:
- `--listening-address`: The IP address which is used for receiving sphinx packets and listening to client data.
- `--public-ips`: A comma separated list of IPs that are announced to the `nym-api`. In the most cases `--public-ips` **is the address used for bonding.**
- `--listening-address`: The IP address which is used for receiving sphinx packets and listening to client data.
- `--public-ips`: A comma separated list of IPs that are announced to the `nym-api`. In the most cases `--public-ips` **is the address used for bonding.**
```sh
--listening-address 0.0.0.0 --public-ips "$(curl -4 https://ifconfig.me)"
```
- `--hostname` (optional): This flag is required if the operator wishes to run WSS. It can be something like `mainnet-gateway2.nymtech.net`.
- `--hostname` (optional): This flag is required if the operator wishes to run WSS. It can be something like `mainnet-gateway2.nymtech.net`.
2. Make sure to enable all necessary [ports](maintenance.md#configure-your-firewall) on the Gateway:
@@ -42,7 +42,7 @@ sudo ufw allow 1789,1790,8000,9000,9001,22/tcp, 9001/tcp
The Gateway will then be accessible on something like: *http://85.159.211.99:8080/api/v1/swagger/index.html*
Are you seeing something like: *this node attempted to announce an invalid public address: 0.0.0.0.*?
Are you seeing something like: *this node attempted to announce an invalid public address: 0.0.0.0.*?
Please modify `[host.public_ips]` section of your config file stored as `~/.nym/gateways/<ID>/config/config.toml`.
@@ -92,7 +92,7 @@ curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo
apt update
apt install caddy
systemctl enable caddy.service
systemctl enable caddy.service
cd /etc/caddy
@@ -102,7 +102,7 @@ if [ -f Caddyfile ]; then
rm -f Caddyfile
fi
cat <<EOF >> Caddyfile
cat <<EOF >> Caddyfile
${host}:${port_value} {
@websockets {
header Connection *Upgrade*
@@ -191,9 +191,9 @@ nohup ./<NODE> run --id <YOUR_ID> # where `<YOUR_ID>` is the id you set during t
#### tmux
One way is to use `tmux` shell on top of your current VPS terminal. Tmux is a terminal multiplexer, it allows you to create several terminal windows and panes from a single terminal. Processes started in `tmux` keep running after closing the terminal as long as the given `tmux` window was not terminated.
One way is to use `tmux` shell on top of your current VPS terminal. Tmux is a terminal multiplexer, it allows you to create several terminal windows and panes from a single terminal. Processes started in `tmux` keep running after closing the terminal as long as the given `tmux` window was not terminated.
Use the following command to get `tmux`.
Use the following command to get `tmux`.
Platform|Install Command
---|---
@@ -204,15 +204,15 @@ RHEL or CentOS|`yum install tmux`
macOS (using Homebrew|`brew install tmux`
macOS (using MacPorts)|`port install tmux`
openSUSE|`zypper install tmux`
In case it didn't work for your distribution, see how to build `tmux` from [version control](https://github.com/tmux/tmux#from-version-control).
In case it didn't work for your distribution, see how to build `tmux` from [version control](https://github.com/tmux/tmux#from-version-control).
**Running tmux**
Now you have installed tmux on your VPS, let's run a Mix Node on tmux, which allows you to detach your terminal and let your `<NODE>` run on its own on the VPS.
* Pause your `<NODE>`
* Start tmux with the command
* Start tmux with the command
```sh
tmux
```
@@ -230,7 +230,7 @@ tmux attach-session
#### systemd
To automate with `systemd` use this init service file and follow the steps below.
To automate with `systemd` use this init service file and follow the steps below.
##### For Mix Node
@@ -327,18 +327,18 @@ Change the `<PATH>` in `ExecStart` to point at your `<NODE>` binary (`nym-mixnod
Example: If you have built nym in the `$HOME` directory on your server, your username is `jetpanther`, and node `<ID>` is `puma`, then the `ExecStart` line (command) in the script located in `/etc/systemd/system/nym-mixnode.service` for Nym Mixnode might look like this:
`ExecStart=/home/jetpanther/nym/target/release/nym-mixnode run --id puma`.
`ExecStart=/home/jetpanther/nym/target/release/nym-mixnode run --id puma`.
Basically, you want the full `/<PATH>/<TO>/nym-mixnode run --id <WHATEVER-YOUR-NODE-ID-IS>`. If you are unsure about your `/<PATH>/<TO>/<NODE>`, then `cd` to your directory where you run your `<NODE>` from and run `pwd` command which returns the full path for you.
Once done, save the script and follow these steps:
```sh
systemctl daemon-reload
systemctl daemon-reload
# to pickup the new unit file
```
Enable the newly created service:
Enable the newly created service:
```sh
# for Mix Node
@@ -350,7 +350,7 @@ systemctl enable nym-gateway.service
# for Network Requester
systemctl enable nym-network-requester.service
# for Nymvisor
# for Nymvisor
systemctl enable nymvisor.service
```
@@ -367,7 +367,7 @@ service nym-gateway start
service nym-network-requester.service
# for Nymvisor
service nymvisor.service start
service nymvisor.service start
```
This will cause your `<NODE>` to start at system boot time. If you restart your machine, your `<NODE>` will come back up automatically.
@@ -427,7 +427,7 @@ systemctl start nymd # to actually start the service
journalctl -f -u nymd # to monitor system logs showing the service start
```
##### For Nym API
##### For Nym API
Below is a `systemd` unit file to place at `/etc/systemd/system/nym-api.service` to automate your API instance:
@@ -535,7 +535,7 @@ In case you chose tmux option for Mix Node automation, see your `ulimit` list by
ulimit -a
# watch for the output line -n
-n: file descriptors 1024
-n: file descriptors 1024
```
You can change it either by running a command:
@@ -582,14 +582,14 @@ scp -r -3 <SOURCE_USER_NAME>@<SOURCE_HOST_ADDRESS>:~/.nym/mixnodes/<YOUR_ID> <TA
```
* Re-run init (remember that init doesn't overwrite existing keys) to generate a config with the new listening address etc.
* Change the node smart contract info via the wallet interface. Otherwise the keys will point to the old IP address in the smart contract, and the node will not be able to be connected, and it will fail up-time checks.
* Re-run the node from the new location.
* Re-run the node from the new location.
## Virtual IPs and hosting via Google & AWS
For true internet decentralization we encourage operators to use diverse VPS providers instead of the largest companies offering such services. If for some reasons you have already running AWS or Google and want to setup a `<NODE>` there, please read the following.
On some services (AWS, Google, etc) the machine's available bind address is not the same as the public IP address. In this case, bind `--host` to the local machine address returned by `$(curl -4 https://ifconfig.me)`, but that may not the public IP address to bond your `<NODE>` in the wallet.
On some services (AWS, Google, etc) the machine's available bind address is not the same as the public IP address. In this case, bind `--host` to the local machine address returned by `$(curl -4 https://ifconfig.me)`, but that may not the public IP address to bond your `<NODE>` in the wallet.
You can run `ifconfig` command. For example, on a Google machine, you may see the following output:
@@ -601,7 +601,7 @@ ens4: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1460
The `ens4` interface has the IP `10.126.5.7`. But this isn't the public IP of the machine, it's the IP of the machine on Google's internal network. Google uses virtual routing, so the public IP of this machine is something else, maybe `36.68.243.18`.
To find the right IP configuration, contact your VPS provider for support to find the right public IP and use it to bond your `<NODE>` with the `nym-api` via Nym wallet.
To find the right IP configuration, contact your VPS provider for support to find the right public IP and use it to bond your `<NODE>` with the `nym-api` via Nym wallet.
On self-hosted machine it's a bit more tricky. In that case as an operator you must be sure that your ISP allows for public IPv4 and IPv6 and then it may be a bit of playing around to find the right configuration. One way may be to bind your binary with the `--host` flag to local address `127.0.0.1` and run `echo "$(curl -4 https://ifconfig.me)"` to get a public address which you use to bond your Mix Node to `nym-api` via Nym wallet.
@@ -650,7 +650,7 @@ Query Response:
### Validator: Installing and configuring nginx for HTTPS
#### Setup
[Nginx](https://www.nginx.com/resources/glossary/nginx/#:~:text=NGINX%20is%20open%20source%20software,%2C%20media%20streaming%2C%20and%20more.&text=In%20addition%20to%20its%20HTTP,%2C%20TCP%2C%20and%20UDP%20servers.) is an open source software used for operating high-performance web servers. It allows us to set up reverse proxying on our validator server to improve performance and security.
[Nginx](https://www.nginx.com/resources/glossary/nginx) is an open source software used for operating high-performance web servers. It allows us to set up reverse proxying on our validator server to improve performance and security.
Install `nginx` and allow the 'Nginx Full' rule in your firewall:
@@ -678,15 +678,16 @@ Which should return:
└─2380 nginx: worker process
```
#### Configuration
#### Full Node Configuration
Proxying your validator's port `26657` to nginx port `80` can then be done by creating a file with the following at `/etc/nginx/conf.d/validator.conf`:
Proxying various full node services through port 80 can then be done by creating a file with the following at `/etc/nginx/sites-enabled/nyxd-webrequests.conf`:
```sh
### To expose RPC server
server {
listen 80;
listen [::]:80;
server_name "domain_name";
server_name "<rpc.nyx.yourdomain.tld>";
location / {
proxy_pass http://127.0.0.1:26657;
@@ -695,20 +696,58 @@ server {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
### To expose Cosmos API server
server {
server_name "<api.nyx.yourdomain.tld>";
location / {
proxy_pass http://127.0.0.1:1317;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Host $http_host;
proxy_set_header Upgrade websocket;
proxy_set_header Connection Upgrade;
}
}
### To expose GRPC endpoint
server {
server_name "<grpc.nyx.yourdomain.tld>";
location / {
grpc_pass 127.0.0.1:9090;
}
}
```
#### nym-api Configuration
```sh
### To expose nym-api webserver
server {
listen 80;
listen [::]:80;
server_name "<nym-api.nyx.yourdomain.tld>";
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
```
Followed by:
```sh
sudo apt install certbot nginx python3
certbot --nginx -d nym-validator.yourdomain.com -m you@yourdomain.com --agree-tos --noninteractive --redirect
certbot --nginx -m <you@yourdomain.com> --agree-tos
```
```admonish caution title=""
If using a VPS running Ubuntu 20: replace `certbot nginx python3` with `python3-certbot-nginx`
```
These commands will get you an https encrypted nginx proxy in front of the API.
These commands will get you an https encrypted nginx proxy in front of the various endpoints.
### Configuring Prometheus metrics (optional)
@@ -795,6 +834,3 @@ All validator-specific port configuration can be found in `$HOME/.nymd/config/co
| 1317 | REST API server endpoint |
| 26656 | Listen for incoming peer connections |
| 26660 | Listen for Prometheus connections |
+41 -23
View File
@@ -1,33 +1,48 @@
# Nym API Setup
[//]: # (> The Nym API binary was built in the [building nym]&#40;../binaries/building-nym.md&#41; section. If you haven't yet built Nym and want to run the code, go there first. You can build just the API with `cargo build --release --bin nym-api`.)
[//]: # ()
[//]: # (> The nym-api binary was built in the [building nym]&#40;../binaries/building-nym.md&#41; section. If you haven't yet built Nym and want to run the code, go there first. You can build just the API with `cargo build --release --bin nym-api`.)
> The `nym-api` binary should be coming out in the next release - we're releasing this document beforehand so that Validators have information as soon as possible and get an idea of what to expect. This doc will be expanded over time as we release the API binary itself as well as start enabling functionality.
> The `nym-api` binary will be released in the immediate future - we're releasing this document beforehand so that Validators have information as soon as possible and get an idea of what to expect. This doc will be expanded over time as we release the API binary itself as well as start enabling functionality.
>
> You can build the API with `cargo build --release --bin nym-api`.
> Any syntax in `<>` brackets is a user's unique variable. Exchange with a corresponding name without the `<>` brackets.
## What is the Nym API?
The Nym API is a binary that will be operated by the Nyx Blockchain Validator set. This binary can be run in several different modes, and has two main bits of functionality:
* network monitoring (calculating the routing score of Mixnet nodes)
The Nym API is a binary that will be operated by the Nyx validator set. This binary can be run in several different modes, and has two main bits of functionality:
* network monitoring (calculating the routing score of Mixnet nodes)
* generation and validation of [zk-Nyms](https://blog.nymtech.net/zk-nyms-are-here-a-major-milestone-towards-a-market-ready-mixnet-a3470c9ab10a), our implementation of the Coconut Selective Disclosure Credential Scheme.
This is important for both the proper decentralisation of the network uptime calculation and, more pressingly, enabling the NymVPN to utilise privacy preserving payments.
This is important for both the proper decentralisation of the network uptime calculation and, more pressingly, enabling the NymVPN to utilise privacy preserving payments.
The process of enabling these different aspects of the system will take time. Operators of the Nym API binary for the moment will only have to run the binary in a minimal 'caching' mode in order to get used to maintaining an additional process running alongside their Validator.
The process of enabling these different aspects of the system will take time. At the moment, Nym API operators will only have to run the binary in a minimal 'caching' mode in order to get used to maintaining an additional process running alongside a full node.
### Rewards
Operators of Nym API instances will be rewarded for performing the extra work of taking part in credential generation. These rewards will be calculated **separately** from rewards for block production.
```admonish warning
It is highly recommended to run `nym-api` alongside a full node since you will be exposing HTTP port(s) to the Internet. We also observed degradation in p2p and block signing operations when `nym-api` was run alongside a signing validator.
```
Rewards for credential signing will be calculated hourly, with API operators receiving a proportional amount of the reward pool (333NYM per hour / 237600NYM per month) according to the % of credentials they have signed.
### Rewards
Operators of Nym API will be rewarded for performing the extra work of taking part in credential generation. These rewards will be calculated **separately** from rewards for block production.
### (Coming Soon) Machine Specs
We are working on load testing currently in order to get good specs for a Validator + Nym API setup. Bear in mind that credential signing is primarily CPU-bound.
Rewards for credential signing will be calculated hourly, with API operators receiving a proportional amount of the reward pool (333NYM per hour / 237,600 NYM per month), proportional to the percentage of credentials they have signed.
### (Coming Soon) Machine Specs
We are working on load testing currently in order to get good specs for a full node + Nym API setup. Bear in mind that credential signing is primarily CPU-bound.
### (Coming Soon) Credential Generation
Validators that take part in the DKG ceremony (more details on this soon) will become part of the quorum generating and verifying zk-Nym credentials. These will initially be used for private proof of payment for NymVPN (see our blogposts [here](https://blog.nymtech.net/nymvpn-an-invitation-for-privacy-experts-and-enthusiasts-63644139d09d) and [here](https://blog.nymtech.net/zk-nyms-are-here-a-major-milestone-towards-a-market-ready-mixnet-a3470c9ab10a) for more on this), and in the future will be expanded into more general usecases such as [offline ecash](https://arxiv.org/abs/2303.08221).
Validators that take part in the DKG ceremony will become part of the 'quorum' generating and verifying zk-Nym credentials. These will initially be used for private proof of payment for NymVPN (see our blogposts [here](https://blog.nymtech.net/nymvpn-an-invitation-for-privacy-experts-and-enthusiasts-63644139d09d) and [here](https://blog.nymtech.net/zk-nyms-are-here-a-major-milestone-towards-a-market-ready-mixnet-a3470c9ab10a) for more on this), and in the future will be expanded into more general usecases such as [offline ecash](https://arxiv.org/abs/2303.08221).
## Current version
The DKG ceremony will be used to create a subset of existing validators - referred to as the quorum. As outlined above, they will be the ones taking part in the generation and verification of zk-Nym credentials. The size of the 'minimum viable quorum' is 10 - we are aiming for a larger number than this for the initial quorum in order to have some redundancy in the case of a Validator dropping or going offline.
We will be releasing more detailed step-by-step documentation for involved validators nearer to the ceremony itself, but at a high level it will involve:
* the deployment and initialisation of [`group`](https://github.com/nymtech/nym/tree/develop/contracts/multisig/cw4-group) and [`multisig`](https://github.com/nymtech/nym/tree/develop/contracts/multisig) contracts by Nym. Validators that are members of the `group` contract are the only ones that will be able to take part in the ceremony.
* the deployment and initialisation of an instance of the [DKG contract](https://github.com/nymtech/nym/tree/develop/contracts/coconut-dkg) by Nym.
* Validators will update their `nym-api` configs with the address of the deployed contracts. They will also stop running their API instance in caching only mode, instead switching over run with the `--enabled-credentials-mode`.
* From the perspective of operators, this is all they have to do. Under the hood, each `nym-api` instance will then take part in several rounds of key submission, verification, and derivation. This will continue until quorum is acheived. More information on this will be released closer to the time of the ceremony.
**We will be communicating individually with members of the existing Validator set who have expressed interest in joining the quorum concerning the timing and specifics of the ceremony**.
## Current version
```
<!-- cmdrun ../../../../target/release/nym-api --version | grep "Build Version" | cut -b 21-26 -->
```
@@ -54,31 +69,31 @@ You can also check the various arguments required for individual commands with:
./nym-api <COMMAND> --help
```
### Initialising your Nym API Instance
Initialise your API instance with:
### Initialising your Nym API Instance
Initialise your API instance with:
```
./nym-api init
```
You can optionally pass a local identifier for this instance with the `--id` flag. Otherwise the ID of your instance defaults to `default`.
You can optionally pass a local identifier for this instance with the `--id` flag. Otherwise the ID of your instance defaults to `default`.
### Running your Nym API Instance
The API binary currently defaults to running in caching mode. You can run your API with:
### Running your Nym API Instance
The API binary currently defaults to running in caching mode. You can run your API with:
```
./nym-api run
```
By default the API will be trying to query a running `nyxd` process (either a validator or RPC node) on `localhost:26657`. This value can be modified either via the `--nyxd-validator ` flag on `run`:
By default the API will be trying to query a running `nyxd` process (either a validator or RPC node) on `localhost:26657`. This value can be modified either via the `--nyxd-validator ` flag on `run`:
```
./nym-api run --nyxd-validator https://rpc.nymtech.net:443
./nym-api run --nyxd-validator https://rpc.nymtech.net:443
```
> You can also change the value of `local_validator` in the config file found by default in `$HOME/.nym/nym-api/<ID>/config/config.toml`.
This process is quite noisy, but informative:
This process is quite noisy, but informative:
~~~admonish example collapsible=true title="Console output"
```
@@ -160,5 +175,8 @@ Starting nym api...
```
~~~
## Automation
## Automation
You will most likely want to automate your validator restarting if your server reboots. Checkout the [maintenance page](./maintenance.md) for an example `service` file.
## Exposing web endpoint using HTTPS
It is recommended to expose the webserver over HTTPS by using a webserver like Nginx. An example configuration for configuring Nginx is listed [on the maintenance page](maintenance.md#nym-api-configuration)
@@ -216,7 +216,7 @@ You can use the following command to download them for the correct network:
wget -O $HOME/.nyxd/config/genesis.json https://nymtech.net/genesis/genesis.json
# Sandbox testnet
wget -O $HOME/.nyxd/config/genesis.json https://rpc.sandbox.nymtech.net/snapshots/genesis.json | jq '.result.genesis'
curl https://rpc.sandbox.nymtech.net/snapshots/genesis.json | jq '.result.genesis' > $HOME/.nyxd/config/genesis.json
```
### `config.toml` configuration
@@ -254,7 +254,7 @@ And if you wish to add a human-readable moniker to your node:
Finally, if you plan on using [Cockpit](https://cockpit-project.org/documentation.html) on your server, change the `grpc` port from `9090` as this is the port used by Cockpit.
### `app.toml` configuration
In the file `$HOME/nyxd/config/app.toml`, set the following values:
In the file `$HOME/.nyxd/config/app.toml`, set the following values:
```
# Mainnet
@@ -264,7 +264,6 @@ minimum-gas-prices = "0.025unym,0.025unyx"
```
# Sandbox Testnet
minimum-gas-prices = "0.025unym,0.025unyx"
enable = true` in the `[api]` section to get the API server running
```
### Setting up your validator's admin user
@@ -304,6 +303,31 @@ File at /path/to/genesis.json is a valid genesis file
> If this test did not pass, check that you have replaced the contents of `/<PATH-TO>/.nyxd/config/genesis.json` with that of the correct genesis file.
### Setting up nyxd as full node (non-signing)
```admonish danger title=""
Skip this section if you're planning to run a validator node to join network consensus. To ensure security & maximum availability of validators, do not expose additional services to the Internet
```
Unlike signing validators, full nodes do not propose / sign blocks. A full node is typically used for indexing blocks produced on the chain and for exposing web interfaces such as RPC, API and gRPC endpoints required for external applications/services to interact with the blockchain.
By default, API server is disabled and RPC/gRPC servers listen to the loopback address only. In a production setup, it is recommended to use a webserver such as Nginx or caddy to proxy requests to the endpoints as required.
To enable Cosmos REST API, you can enable it in `$HOME/.nyxd/config/app.toml` like :
```
[api]
# Enable defines if the API server should be enabled. Toggle this to `true`
enable = true
# Swagger defines if swagger documentation should automatically be registered.
# You can also expose swagger documentation by toggling the below configuration to true
swagger = true
```
For more information on enabling access to various endpoints via Nginx, refer to the [example configuration here](./maintenance.md#setup)
### Open firewall ports
Before starting the validator, we will need to open the firewall ports:
@@ -319,6 +343,9 @@ sudo ufw enable
# 22 : Default SSH port
sudo ufw allow 26656,26660,22
## !! FOR FULL NODES ONLY !! - exposing Nginx for serving web requests
sudo ufw allow 80,443
# to check everything worked
sudo ufw status
```
@@ -358,10 +385,8 @@ lz4 -c -d nyxd-sandbox-snapshot-data.tar.lz4 | tar -x -C $HOME/.nyxd
You can then restart `nyxd` - it should start syncing from a block > 2000000.
### Joining Consensus
```admonish caution title=""
When joining consensus, make sure that you do not disrupt (or worse - halt) the network by coming in with a disproportionately large amount of staked tokens.
Please initially stake a small amount of tokens compared to existing validators, then delegate to yourself in tranches over time.
```admonish info title=""
You can skip this section if you are planning to run a full-node. This step will make your node a signing validator which joins network consensus
```
Once your validator has synced and you have received tokens, you can join consensus and produce blocks.
@@ -473,11 +498,15 @@ To upgrade your validator, follow the steps on the [maintenance page](./maintena
#### Common reasons for your validator being jailed
The most common reason for your validator being jailed is that your validator is out of memory because of bloated syslogs.
Your validator will be jailed if your node:
- misses _`x`_ amount of blocks in _`y`_ interval, where _`x`_ and _`y`_ are parameters set by chain governance
- performs double signing (two conflicting signatures on the same block using the same key)
Running the command `df -H` will return the size of the various partitions of your VPS.
Double signing is a serious infraction. If a node double signs, all the delegators to the node (including self-delegation) will be slashed by 5%. Additionally, the node will be permanently jailed and removed from consensus (called _tombstoning_)
If the `/dev/sda` partition is almost full, try pruning some of the `.gz` syslog archives and restart your validator process.
One of the most common reason for your validator being jailed is that your validator is out of memory because of bloated syslogs.
Running the command `df -H` will return the size of the various partitions of your VPS. If the partition with blockchain data is almost full, try pruning the blockchain data or expanding the storage size.
### Day 2 operations with your validator
+8
View File
@@ -0,0 +1,8 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm
NymVPM.xcworkspace/
View File
+149
View File
@@ -0,0 +1,149 @@
# List of rules with definitions
# https://github.com/realm/SwiftLint/blob/master/Rules.md
disabled_rules:
- todo
opt_in_rules:
- anyobject_protocol
- attributes
- balanced_xctest_lifecycle
- capture_variable
- closure_body_length
- closure_end_indentation
- closure_parameter_position
- closure_spacing
- collection_alignment
- colon
- conditional_returns_on_newline
- contains_over_filter_count
- contains_over_filter_is_empty
- contains_over_first_not_nil
- contains_over_range_nil_comparison
- discouraged_none_name
- discouraged_object_literal
- empty_collection_literal
- empty_count
- empty_string
- empty_xctest_method
- enum_case_associated_values_count
- expiring_todo
- explicit_init
- fallthrough
- fatal_error_message
- file_header
- file_name
- file_name_no_space
- first_where
- flatmap_over_map_reduce
- force_cast
- force_try
- force_unwrapping
- function_default_parameter_at_end
- identical_operands
- implicit_return
- indentation_width
- joined_default_parameter
- last_where
- legacy_random
- line_length
- literal_expression_end_indentation
- lower_acl_than_parent
- multiline_arguments
- multiline_arguments_brackets
- multiline_function_chains
- multiline_literal_brackets
- multiline_parameters
- multiline_parameters_brackets
- operator_usage_whitespace
- overridden_super_call
- pattern_matching_keywords
- prefer_self_type_over_type_of_self
- prefer_zero_over_explicit_init
- private_action
- private_outlet
- prohibited_interface_builder
- prohibited_super_call
- redundant_nil_coalescing
- redundant_type_annotation
- sorted_first_last
- switch_case_on_newline
- test_case_accessibility
- toggle_bool
- trailing_closure
- unavailable_function
- unneeded_parentheses_in_closure_argument
- unused_declaration
- unused_import
- vertical_parameter_alignment_on_call
- vertical_whitespace_closing_braces
- weak_delegate
- xct_specific_matcher
- yoda_condition
attributes:
always_on_same_line:
- "@IBSegueAction"
- "@IBAction"
- "@NSManaged"
- "@objc"
- "@MainActor"
- "@discardableResult"
conditional_returns_on_newline:
if_only: true
cyclomatic_complexity:
ignores_case_statements: true
enum_case_associated_values_count:
warning: 4
error: 6
function_parameter_count:
warning: 6
error: 8
identifier_name:
min_length: 2
max_length: 60
indentation_width:
indentation_width: 4
legacy_hashing: error
line_length:
ignores_urls: true
ignores_comments: true
ignores_function_declarations: false
ignores_interpolated_strings: true
warning: 120
error: 240
multiline_parameters:
allowsSingleLine: false
nesting:
type_level: 3
private_outlet:
allow_private_set: true
trailing_closure:
only_single_muted_parameter: true
trailing_whitespace:
ignores_comments: true
type_body_length:
warning: 400
error: 500
type_name:
min_length: 2
max_length: 60
warning_threshold: 15
reporter: "xcode"
+8
View File
@@ -0,0 +1,8 @@
.DS_Store
/.build
/Packages
xcuserdata/
DerivedData/
.swiftpm/configuration/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
+36
View File
@@ -0,0 +1,36 @@
// swift-tools-version: 5.9
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "Home",
defaultLocalization: "en",
platforms: [
.iOS(.v16)
],
products: [
.library(
name: "Home",
targets: ["Home"]
)
],
dependencies: [
.package(path: "../UIComponents"),
.package(path: "../Settings")
],
targets: [
.target(
name: "Home",
dependencies: [
"UIComponents",
"Settings"
],
path: "Sources"
),
.testTarget(
name: "HomeTests",
dependencies: ["Home"]
)
]
)
@@ -0,0 +1,29 @@
import SwiftUI
import Settings
struct HomeFlowCoordinator<Content: View>: View {
@ObservedObject var state: HomeFlowState
let content: () -> Content
var body: some View {
NavigationStack(path: $state.path) {
ZStack {
content()
}
.navigationDestination(for: HomeLink.self, destination: linkDestination)
}
}
}
private extension HomeFlowCoordinator {
@ViewBuilder private func linkDestination(link: HomeLink) -> some View {
switch link {
case .firstHop(text: _):
HopListView(viewModel: HopListViewModel(path: $state.path, type: .first))
case .lastHop:
HopListView(viewModel: HopListViewModel(path: $state.path, type: .last))
case .settings:
SettingsView(viewModel: SettingsViewModel(path: $state.path))
}
}
}
@@ -0,0 +1,7 @@
import SwiftUI
public class HomeFlowState: ObservableObject {
@Published var path = NavigationPath()
@Published var presentedItem: HomeLink?
@Published var coverItem: HomeLink?
}
@@ -0,0 +1,11 @@
import Foundation
enum HomeLink: Hashable, Identifiable {
case firstHop(text: String?)
case lastHop
case settings
var id: String {
String(describing: self)
}
}
@@ -0,0 +1,125 @@
import SwiftUI
import AppSettings
import Modifiers
import Theme
import UIComponents
public struct HomeView: View {
@ObservedObject private var viewModel: HomeViewModel
public init(viewModel: HomeViewModel) {
self.viewModel = viewModel
}
public var body: some View {
HomeFlowCoordinator(state: viewModel, content: content)
}
}
private extension HomeView {
@ViewBuilder
func content() -> some View {
VStack {
navbar()
Spacer()
statusAreaSection()
Spacer()
networkSection()
connectionSection()
connectButton()
}
.appearanceUpdate()
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background {
NymColor.background
.ignoresSafeArea()
}
}
@ViewBuilder
func navbar() -> some View {
CustomNavBar(
title: "NymVPN".localizedString,
rightButton: CustomNavBarButton(type: .settings, action: { viewModel.navigateToSettings() })
)
Spacer()
.frame(height: 50)
}
@ViewBuilder
func statusAreaSection() -> some View {
StatusButton(config: .disconnected)
Spacer()
.frame(height: 8)
StatusInfoView()
Spacer()
.frame(height: 24)
}
@ViewBuilder
func networkSection() -> some View {
HStack {
Text("selectNetwork".localizedString)
.textStyle(.Title.Medium.primary)
Spacer()
}
.padding(.horizontal, 16)
Spacer()
.frame(height: 24)
NetworkButton(viewModel: NetworkButtonViewModel(type: .mixnet, selectedNetwork: $viewModel.selectedNetwork))
.padding(EdgeInsets(top: 0, leading: 16, bottom: 16, trailing: 16))
.onTapGesture {
viewModel.selectedNetwork = .mixnet
}
NetworkButton(viewModel: NetworkButtonViewModel(type: .wireguard, selectedNetwork: $viewModel.selectedNetwork))
.padding(.horizontal, 16)
.onTapGesture {
viewModel.selectedNetwork = .wireguard
}
Spacer()
.frame(height: 32)
}
@ViewBuilder
func connectionSection() -> some View {
HStack {
Text("connectTo".localizedString)
.foregroundStyle(NymColor.sysOnSurfaceWhite)
.textStyle(.Title.Medium.primary)
Spacer()
}
.padding(.horizontal, 16)
Spacer()
.frame(height: 24)
VStack {
HopButton(hopType: .first, country: Country(name: "Germany", code: "de"))
.onTapGesture {
viewModel.navigateToFirstHopSelection()
}
Spacer()
.frame(height: 24)
HopButton(hopType: .last, country: Country(name: "Switzerland", code: "ch"))
.onTapGesture {
viewModel.navigateToLastHopSelection()
}
}
.padding(.horizontal, 16)
Spacer()
.frame(height: 32)
}
@ViewBuilder
func connectButton() -> some View {
ConnectButton()
.padding(.horizontal, 16)
.onTapGesture {
viewModel.connect()
}
}
}
@@ -0,0 +1,47 @@
import SwiftUI
import UIComponents
import Tunnels
public class HomeViewModel: HomeFlowState {
private let tunnelsManager: TunnelsManager
@Published var selectedNetwork: NetworkButtonViewModel.ButtonType
public init(
selectedNetwork: NetworkButtonViewModel.ButtonType,
tunnelsManager: TunnelsManager = TunnelsManager.shared
) {
self.selectedNetwork = selectedNetwork
self.tunnelsManager = tunnelsManager
tunnelsManager.loadConfigurations()
}
}
// MARK: - Navigation -
public extension HomeViewModel {
func navigateToSettings() {
path.append(HomeLink.settings)
}
func navigateToFirstHopSelection() {
path.append(HomeLink.firstHop(text: ""))
}
func navigateToLastHopSelection() {
path.append(HomeLink.lastHop)
}
}
// MARK: - Tunnel testing -
public extension HomeViewModel {
func connect() {
if let tunnel = tunnelsManager.currentTunnel, tunnel.tunnel.connection.status == .connected {
tunnelsManager.disconnect()
} else {
tunnelsManager.test()
}
}
}
@@ -0,0 +1,135 @@
import SwiftUI
import Theme
import UIComponents
public struct HopListView: View {
private let viewModel: HopListViewModel
public init(viewModel: HopListViewModel) {
self.viewModel = viewModel
}
public var body: some View {
VStack(spacing: 0) {
navbar()
Spacer()
.frame(height: 24)
countryButton()
Spacer()
.frame(height: 24)
countryButton2()
Spacer()
.frame(height: 24)
searchView()
Spacer()
.frame(height: 24)
availableCountryList()
}
.navigationBarBackButtonHidden(true)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background {
NymColor.background
.ignoresSafeArea()
}
}
}
private extension HopListView {
@ViewBuilder
func navbar() -> some View {
CustomNavBar(
title: viewModel.type.selectHopLocalizedTitle,
leftButton: CustomNavBarButton(type: .back, action: { viewModel.navigateHome() })
)
}
@ViewBuilder
func countryButton() -> some View {
CountryCellButton(
viewModel: CountryCellButtonViewModel(
type: .fastest(
country: Country(name: "Germany", code: "de")
),
isSelected: false
)
)
.padding(.horizontal, 15)
}
@ViewBuilder
func countryButton2() -> some View {
CountryCellButton(
viewModel: CountryCellButtonViewModel(
type: .country(
country: Country(name: "Germany", code: "de")
),
isSelected: true
)
)
.padding(.horizontal, 15)
}
@ViewBuilder
func searchView() -> some View {
SearchView(viewModel: SearchViewModel())
.padding(.horizontal, 40)
}
@ViewBuilder
func availableCountryList() -> some View {
ScrollView {
switzerland()
germany()
switzerland()
germany()
switzerland()
germany()
switzerland()
germany()
switzerland()
germany()
switzerland()
germany()
switzerland()
germany()
switzerland()
germany()
switzerland()
germany()
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.ignoresSafeArea(.all)
}
@ViewBuilder
func germany() -> some View {
CountryCellButton(
viewModel: CountryCellButtonViewModel(
type: .country(
country: Country(name: "Germany", code: "de")
),
isSelected: false
)
)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.ignoresSafeArea(.all)
}
@ViewBuilder
func switzerland() -> some View {
CountryCellButton(
viewModel: CountryCellButtonViewModel(
type: .country(
country: Country(name: "Switzerland", code: "ch")
),
isSelected: false
)
)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.ignoresSafeArea(.all)
}
}
@@ -0,0 +1,18 @@
import SwiftUI
public struct HopListViewModel {
public let type: HopType
@Binding var path: NavigationPath
public init(path: Binding<NavigationPath>, type: HopType) {
self.type = type
_path = path
}
}
extension HopListViewModel {
func navigateHome() {
path = .init()
}
}
@@ -0,0 +1,15 @@
import Foundation
public enum HopType {
case first
case last
var selectHopLocalizedTitle: String {
switch self {
case .first:
"firstHopSelection".localizedString
case .last:
"lastHopSelection".localizedString
}
}
}
@@ -0,0 +1,4 @@
import XCTest
@testable import Home
final class HomeTests: XCTestCase {}
+13
View File
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.networkextension.packet-tunnel</string>
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).PacketTunnelProvider</string>
</dict>
</dict>
</plist>
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.networking.networkextension</key>
<array>
<string>packet-tunnel-provider</string>
</array>
<key>com.apple.security.application-groups</key>
<array>
<string>group.net.nymtech.vpn</string>
</array>
</dict>
</plist>
@@ -0,0 +1,45 @@
import NetworkExtension
import OSLog
class PacketTunnelProvider: NEPacketTunnelProvider {
override func startTunnel(options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void) {
NSLog("🏁 Starting tunnel with options: \(options ?? [:])")
os_log("🏁 Starting tunnel with options: \(options ?? [:])")
fetchData()
completionHandler(nil)
}
func fetchData() {
// Do not use the NSTimer here that will not run in background
let popTime = DispatchTime.now() + DispatchTimeInterval.seconds(Int(1))
DispatchQueue.global(qos: .background).asyncAfter(deadline: popTime) { [weak self] in
// Fetch your data from server and generate local notification by using UserNotifications framework
self?.doSomeStuff()
self?.fetchData()
}
}
@objc func doSomeStuff() {
NSLog("🔥 ROKAS TIMER TIC TAK")
os_log("🔥 ROKAS TIMER TIC TAK")
}
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
NSLog("🛑 Rokas stopping tunnel")
completionHandler()
}
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) {
if let handler = completionHandler {
handler(messageData)
}
}
override func sleep(completionHandler: @escaping () -> Void) {
completionHandler()
}
override func wake() {}
}
@@ -0,0 +1,622 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 56;
objects = {
/* Begin PBXBuildFile section */
D9264E262B3DA062004594A8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D9264E252B3DA062004594A8 /* Assets.xcassets */; };
D954C0B92B51C1CA00C6BAAC /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D954C0A62B51C17200C6BAAC /* NetworkExtension.framework */; };
D954C0BC2B51C1CA00C6BAAC /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D954C0BB2B51C1CA00C6BAAC /* PacketTunnelProvider.swift */; };
D954C0C12B51C1CA00C6BAAC /* NymMixnetTunnel.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = D954C0B82B51C1CA00C6BAAC /* NymMixnetTunnel.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
D96740232B56A67A001F6891 /* Tunnels in Frameworks */ = {isa = PBXBuildFile; productRef = D96740222B56A67A001F6891 /* Tunnels */; };
D968BDC92B6AEA9D003F42E5 /* AppSettings in Frameworks */ = {isa = PBXBuildFile; productRef = D968BDC82B6AEA9D003F42E5 /* AppSettings */; };
D968BDCB2B6BC98E003F42E5 /* AppVersionProvider in Frameworks */ = {isa = PBXBuildFile; productRef = D968BDCA2B6BC98E003F42E5 /* AppVersionProvider */; };
D99A147A2B357E9900F2728B /* NymVPNApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = D99A14792B357E9900F2728B /* NymVPNApp.swift */; };
D99A148A2B35A73F00F2728B /* Home in Frameworks */ = {isa = PBXBuildFile; productRef = D99A14892B35A73F00F2728B /* Home */; };
D99A148C2B35A74200F2728B /* Theme in Frameworks */ = {isa = PBXBuildFile; productRef = D99A148B2B35A74200F2728B /* Theme */; };
D9CD835D2B6C32D4008DA864 /* Modifiers in Frameworks */ = {isa = PBXBuildFile; productRef = D9CD835C2B6C32D4008DA864 /* Modifiers */; };
D9F699E02B4C1862005576A9 /* Settings in Frameworks */ = {isa = PBXBuildFile; productRef = D9F699DF2B4C1862005576A9 /* Settings */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
D954C0BF2B51C1CA00C6BAAC /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = D99A146E2B357E9900F2728B /* Project object */;
proxyType = 1;
remoteGlobalIDString = D954C0B72B51C1CA00C6BAAC;
remoteInfo = NymMixnetTunnel;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
D954C0B32B51C17200C6BAAC /* Embed Foundation Extensions */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 13;
files = (
D954C0C12B51C1CA00C6BAAC /* NymMixnetTunnel.appex in Embed Foundation Extensions */,
);
name = "Embed Foundation Extensions";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
D9264E252B3DA062004594A8 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
D954C0A62B51C17200C6BAAC /* NetworkExtension.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NetworkExtension.framework; path = System/Library/Frameworks/NetworkExtension.framework; sourceTree = SDKROOT; };
D954C0B82B51C1CA00C6BAAC /* NymMixnetTunnel.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = NymMixnetTunnel.appex; sourceTree = BUILT_PRODUCTS_DIR; };
D954C0BB2B51C1CA00C6BAAC /* PacketTunnelProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelProvider.swift; sourceTree = "<group>"; };
D954C0BD2B51C1CA00C6BAAC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
D954C0BE2B51C1CA00C6BAAC /* NymMixnetTunnel.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NymMixnetTunnel.entitlements; sourceTree = "<group>"; };
D99A14762B357E9900F2728B /* NymVPN.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = NymVPN.app; sourceTree = BUILT_PRODUCTS_DIR; };
D99A14792B357E9900F2728B /* NymVPNApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NymVPNApp.swift; sourceTree = "<group>"; };
D9F699E32B4C47DE005576A9 /* Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Bridging-Header.h"; sourceTree = "<group>"; };
D9F699E62B4C89F1005576A9 /* NymVPN.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NymVPN.entitlements; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
D954C0B52B51C1CA00C6BAAC /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
D954C0B92B51C1CA00C6BAAC /* NetworkExtension.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
D99A14732B357E9900F2728B /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
D968BDCB2B6BC98E003F42E5 /* AppVersionProvider in Frameworks */,
D96740232B56A67A001F6891 /* Tunnels in Frameworks */,
D9F699E02B4C1862005576A9 /* Settings in Frameworks */,
D968BDC92B6AEA9D003F42E5 /* AppSettings in Frameworks */,
D99A148C2B35A74200F2728B /* Theme in Frameworks */,
D99A148A2B35A73F00F2728B /* Home in Frameworks */,
D9CD835D2B6C32D4008DA864 /* Modifiers in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
D954C0BA2B51C1CA00C6BAAC /* NymMixnetTunnel */ = {
isa = PBXGroup;
children = (
D954C0BB2B51C1CA00C6BAAC /* PacketTunnelProvider.swift */,
D954C0BD2B51C1CA00C6BAAC /* Info.plist */,
D954C0BE2B51C1CA00C6BAAC /* NymMixnetTunnel.entitlements */,
);
path = NymMixnetTunnel;
sourceTree = "<group>";
};
D967402C2B56CA1A001F6891 /* Resources */ = {
isa = PBXGroup;
children = (
D9F699E32B4C47DE005576A9 /* Bridging-Header.h */,
);
path = Resources;
sourceTree = "<group>";
};
D99A146D2B357E9900F2728B = {
isa = PBXGroup;
children = (
D99A14782B357E9900F2728B /* NymVPN */,
D954C0BA2B51C1CA00C6BAAC /* NymMixnetTunnel */,
D99A14772B357E9900F2728B /* Products */,
D99A14882B35A73F00F2728B /* Frameworks */,
);
sourceTree = "<group>";
};
D99A14772B357E9900F2728B /* Products */ = {
isa = PBXGroup;
children = (
D99A14762B357E9900F2728B /* NymVPN.app */,
D954C0B82B51C1CA00C6BAAC /* NymMixnetTunnel.appex */,
);
name = Products;
sourceTree = "<group>";
};
D99A14782B357E9900F2728B /* NymVPN */ = {
isa = PBXGroup;
children = (
D9F699E62B4C89F1005576A9 /* NymVPN.entitlements */,
D99A14792B357E9900F2728B /* NymVPNApp.swift */,
D9264E252B3DA062004594A8 /* Assets.xcassets */,
D967402C2B56CA1A001F6891 /* Resources */,
);
path = NymVPN;
sourceTree = "<group>";
};
D99A14882B35A73F00F2728B /* Frameworks */ = {
isa = PBXGroup;
children = (
D954C0A62B51C17200C6BAAC /* NetworkExtension.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
D954C0B72B51C1CA00C6BAAC /* NymMixnetTunnel */ = {
isa = PBXNativeTarget;
buildConfigurationList = D954C0C22B51C1CA00C6BAAC /* Build configuration list for PBXNativeTarget "NymMixnetTunnel" */;
buildPhases = (
D954C0B42B51C1CA00C6BAAC /* Sources */,
D954C0B52B51C1CA00C6BAAC /* Frameworks */,
D954C0B62B51C1CA00C6BAAC /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = NymMixnetTunnel;
productName = NymMixnetTunnel;
productReference = D954C0B82B51C1CA00C6BAAC /* NymMixnetTunnel.appex */;
productType = "com.apple.product-type.app-extension";
};
D99A14752B357E9900F2728B /* NymVPN */ = {
isa = PBXNativeTarget;
buildConfigurationList = D99A14842B357E9A00F2728B /* Build configuration list for PBXNativeTarget "NymVPN" */;
buildPhases = (
D99A14722B357E9900F2728B /* Sources */,
D99A14732B357E9900F2728B /* Frameworks */,
D99A14742B357E9900F2728B /* Resources */,
D99A14872B359C8800F2728B /* Run Swiftlint */,
D954C0B32B51C17200C6BAAC /* Embed Foundation Extensions */,
);
buildRules = (
);
dependencies = (
D954C0C02B51C1CA00C6BAAC /* PBXTargetDependency */,
);
name = NymVPN;
packageProductDependencies = (
D99A14892B35A73F00F2728B /* Home */,
D99A148B2B35A74200F2728B /* Theme */,
D9F699DF2B4C1862005576A9 /* Settings */,
D96740222B56A67A001F6891 /* Tunnels */,
D968BDC82B6AEA9D003F42E5 /* AppSettings */,
D968BDCA2B6BC98E003F42E5 /* AppVersionProvider */,
D9CD835C2B6C32D4008DA864 /* Modifiers */,
);
productName = NymVPN;
productReference = D99A14762B357E9900F2728B /* NymVPN.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
D99A146E2B357E9900F2728B /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1520;
LastUpgradeCheck = 1510;
TargetAttributes = {
D954C0B72B51C1CA00C6BAAC = {
CreatedOnToolsVersion = 15.2;
};
D99A14752B357E9900F2728B = {
CreatedOnToolsVersion = 15.1;
};
};
};
buildConfigurationList = D99A14712B357E9900F2728B /* Build configuration list for PBXProject "NymVPN" */;
compatibilityVersion = "Xcode 14.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = D99A146D2B357E9900F2728B;
productRefGroup = D99A14772B357E9900F2728B /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
D99A14752B357E9900F2728B /* NymVPN */,
D954C0B72B51C1CA00C6BAAC /* NymMixnetTunnel */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
D954C0B62B51C1CA00C6BAAC /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
D99A14742B357E9900F2728B /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
D9264E262B3DA062004594A8 /* Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
D99A14872B359C8800F2728B /* Run Swiftlint */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = "Run Swiftlint";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "if [[ \"$(uname -m)\" == arm64 ]]; then\n export PATH=\"/opt/homebrew/bin:$PATH\"\nfi\n\nif which swiftlint > /dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
D954C0B42B51C1CA00C6BAAC /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
D954C0BC2B51C1CA00C6BAAC /* PacketTunnelProvider.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
D99A14722B357E9900F2728B /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
D99A147A2B357E9900F2728B /* NymVPNApp.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
D954C0C02B51C1CA00C6BAAC /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = D954C0B72B51C1CA00C6BAAC /* NymMixnetTunnel */;
targetProxy = D954C0BF2B51C1CA00C6BAAC /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
D954C0C32B51C1CA00C6BAAC /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = NymMixnetTunnel/NymMixnetTunnel.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = VW5DZLFHM5;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = NymMixnetTunnel/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = NymMixnetTunnel;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 17.2;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "net.nymtech.vpn.network-extension";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
D954C0C42B51C1CA00C6BAAC /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = NymMixnetTunnel/NymMixnetTunnel.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = VW5DZLFHM5;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = NymMixnetTunnel/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = NymMixnetTunnel;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 17.2;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "net.nymtech.vpn.network-extension";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
D99A14822B357E9A00F2728B /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
D99A14832B357E9A00F2728B /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
D99A14852B357E9A00F2728B /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = NymVPN/NymVPN.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = VW5DZLFHM5;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = NymVPN;
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = net.nymtech.vpn;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OBJC_BRIDGING_HEADER = "$(PROJECT_DIR)/NymVPN/Resources/Bridging-Header.h";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
D99A14862B357E9A00F2728B /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = NymVPN/NymVPN.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = VW5DZLFHM5;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = NymVPN;
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = net.nymtech.vpn;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OBJC_BRIDGING_HEADER = "$(PROJECT_DIR)/NymVPN/Resources/Bridging-Header.h";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
D954C0C22B51C1CA00C6BAAC /* Build configuration list for PBXNativeTarget "NymMixnetTunnel" */ = {
isa = XCConfigurationList;
buildConfigurations = (
D954C0C32B51C1CA00C6BAAC /* Debug */,
D954C0C42B51C1CA00C6BAAC /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
D99A14712B357E9900F2728B /* Build configuration list for PBXProject "NymVPN" */ = {
isa = XCConfigurationList;
buildConfigurations = (
D99A14822B357E9A00F2728B /* Debug */,
D99A14832B357E9A00F2728B /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
D99A14842B357E9A00F2728B /* Build configuration list for PBXNativeTarget "NymVPN" */ = {
isa = XCConfigurationList;
buildConfigurations = (
D99A14852B357E9A00F2728B /* Debug */,
D99A14862B357E9A00F2728B /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCSwiftPackageProductDependency section */
D96740222B56A67A001F6891 /* Tunnels */ = {
isa = XCSwiftPackageProductDependency;
productName = Tunnels;
};
D968BDC82B6AEA9D003F42E5 /* AppSettings */ = {
isa = XCSwiftPackageProductDependency;
productName = AppSettings;
};
D968BDCA2B6BC98E003F42E5 /* AppVersionProvider */ = {
isa = XCSwiftPackageProductDependency;
productName = AppVersionProvider;
};
D99A14892B35A73F00F2728B /* Home */ = {
isa = XCSwiftPackageProductDependency;
productName = Home;
};
D99A148B2B35A74200F2728B /* Theme */ = {
isa = XCSwiftPackageProductDependency;
productName = Theme;
};
D9CD835C2B6C32D4008DA864 /* Modifiers */ = {
isa = XCSwiftPackageProductDependency;
productName = Modifiers;
};
D9F699DF2B4C1862005576A9 /* Settings */ = {
isa = XCSwiftPackageProductDependency;
productName = Settings;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = D99A146E2B357E9900F2728B /* Project object */;
}
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
+22
View File
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:NymVPN.xcodeproj">
</FileRef>
<FileRef
location = "group:Home">
</FileRef>
<FileRef
location = "group:Services">
</FileRef>
<FileRef
location = "group:Settings">
</FileRef>
<FileRef
location = "group:Theme">
</FileRef>
<FileRef
location = "group:UIComponents">
</FileRef>
</Workspace>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict/>
</plist>
@@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,14 @@
{
"images" : [
{
"filename" : "Icon.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "ch.svg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}
@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-ch" viewBox="0 0 512 512">
<g fill-rule="evenodd" stroke-width="1pt">
<path fill="red" d="M0 0h512v512H0z"/>
<g fill="#fff">
<path d="M96 208h320v96H96z"/>
<path d="M208 96h96v320h-96z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 288 B

@@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "de.svg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}
@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-de" viewBox="0 0 512 512">
<path fill="#ffce00" d="M0 341.3h512V512H0z"/>
<path fill="#000001" d="M0 0h512v170.7H0z"/>
<path fill="red" d="M0 170.7h512v170.6H0z"/>
</svg>

After

Width:  |  Height:  |  Size: 232 B

+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.networking.networkextension</key>
<array>
<string>packet-tunnel-provider</string>
</array>
</dict>
</plist>
+29
View File
@@ -0,0 +1,29 @@
import SwiftUI
import Home
import Theme
import AppSettings
import Tunnels
@main
struct NymVPNApp: App {
init() {
setup()
}
var body: some Scene {
WindowGroup {
NavigationStack {
HomeView(viewModel: HomeViewModel(selectedNetwork: .mixnet))
}
.environmentObject(AppSettings.shared)
.environmentObject(TunnelsManager.shared)
}
}
}
private extension NymVPNApp {
func setup() {
ThemeConfiguration.setup()
}
}
@@ -0,0 +1,6 @@
#ifndef Bridging_Header_h
#define Bridging_Header_h
//#import "libwg.h"
#endif
+8
View File
@@ -0,0 +1,8 @@
.DS_Store
/.build
/Packages
xcuserdata/
DerivedData/
.swiftpm/configuration/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc
+54
View File
@@ -0,0 +1,54 @@
// swift-tools-version: 5.9
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "Services",
defaultLocalization: "en",
platforms: [
.iOS(.v16)
],
products: [
.library(
name: "AppSettings",
targets: ["AppSettings"]
),
.library(
name: "AppVersionProvider",
targets: ["AppVersionProvider"]
),
.library(
name: "Modifiers",
targets: ["Modifiers"]
),
.library(
name: "Tunnels",
targets: ["Tunnels"]
)
],
targets: [
.target(
name: "AppSettings",
dependencies: [],
path: "Sources/Services/AppSettings"
),
.target(
name: "AppVersionProvider",
dependencies: [],
path: "Sources/Services/AppVersionProvider"
),
.target(
name: "Modifiers",
dependencies: [
"AppSettings"
],
path: "Sources/Services/Modifiers"
),
.target(
name: "Tunnels",
dependencies: [],
path: "Sources/Services/Tunnels"
)
]
)
@@ -0,0 +1,7 @@
import SwiftUI
public final class AppSettings: ObservableObject {
public static let shared = AppSettings()
@AppStorage("currentAppearance") public var currentTheme: AppSetting.Appearance = .automatic
}
@@ -0,0 +1,20 @@
import SwiftUI
public struct AppSetting {
public enum Appearance: Int, CaseIterable {
case automatic
case light
case dark
public var colorScheme: ColorScheme? {
switch self {
case .light:
return .light
case .dark:
return .dark
case .automatic:
return ColorScheme(.unspecified)
}
}
}
}
@@ -0,0 +1,11 @@
import Foundation
public enum AppVersionProvider {
public static func appVersion(in bundle: Bundle = .main) -> String {
guard let version = bundle.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String
else {
fatalError("Missing CFBundleShortVersionString")
}
return version
}
}
@@ -0,0 +1,17 @@
import SwiftUI
import AppSettings
public struct AppearanceUpdate: ViewModifier {
@EnvironmentObject private var appSettings: AppSettings
public func body(content: Content) -> some View {
content
.preferredColorScheme(appSettings.currentTheme.colorScheme)
}
}
public extension View {
func appearanceUpdate() -> some View {
modifier(AppearanceUpdate())
}
}
@@ -0,0 +1,23 @@
import NetworkExtension
public final class Tunnel: ObservableObject {
public var tunnel: NETunnelProviderManager
@Published public var isEnabled: Bool
private var observers = [AnyObject]()
public init(tunnel: NETunnelProviderManager) {
self.tunnel = tunnel
isEnabled = tunnel.isEnabled
let configurationChangeNotification = NotificationCenter.default.addObserver(
forName: .NEVPNConfigurationChange,
object: tunnel,
queue: .main
) { [weak self] _ in
print("Tunnel isEnabled: \(tunnel.isEnabled)")
self?.isEnabled = tunnel.isEnabled
}
observers.append(configurationChangeNotification)
}
}
@@ -0,0 +1,109 @@
import NetworkExtension
import OSLog
public final class TunnelsManager: ObservableObject {
public static let shared = TunnelsManager()
public var currentTunnel: Tunnel?
private var observers = [AnyObject]()
private init() {
setup()
}
public func loadConfigurations() {
NETunnelProviderManager.loadAllFromPreferences { [weak self] managers, error in
print("1. Loading VPN Configurations")
if let error {
print("Error: \(String(describing: error))")
}
managers?.forEach {
print("Found VPN Configuration")
print("\($0)")
self?.currentTunnel = Tunnel(tunnel: $0)
}
}
}
public func test() {
loadConfigurations()
os_log("2 Starting test")
if currentTunnel == nil {
let manager = createTestManager()
manager.saveToPreferences { error in
if error == nil {
print("3 Added config Successfully")
} else {
print("3 Failure to add config")
}
}
} else {
print("3 Current tunnel already exists")
currentTunnel?.tunnel.isEnabled = true
}
print("4 Connecting")
connect()
}
public func connect() {
do {
let options = [
NEVPNConnectionStartOptionUsername: "john",
NEVPNConnectionStartOptionPassword: "password"
] as [String: NSObject]
try currentTunnel?.tunnel.connection.startVPNTunnel(options: options)
} catch {
print("FAILED to connect: \(error)")
}
}
public func disconnect() {
print("5 Disconnecting")
// currentTunnel?.tunnel.connection.stopVPNTunnel()
NEVPNManager.shared().loadFromPreferences { [weak self] error in
if let error {
print("Error: \(error)")
}
self?.currentTunnel?.tunnel.connection.stopVPNTunnel()
NEVPNManager.shared().connection.stopVPNTunnel()
}
}
}
private extension TunnelsManager {
func createTestManager() -> NETunnelProviderManager {
let manager = NETunnelProviderManager()
manager.localizedDescription = "NymVPN Mixnet"
let tunnelConfiguration = NETunnelProviderProtocol()
tunnelConfiguration.providerBundleIdentifier = "net.nymtech.vpn.network-extension"
tunnelConfiguration.serverAddress = "127.0.0.1:4009"
tunnelConfiguration.providerConfiguration = [:]
manager.protocolConfiguration = tunnelConfiguration
manager.isEnabled = true
return manager
}
}
private extension TunnelsManager {
func setup() {
registerNotifications()
}
func registerNotifications() {
let statusDidChangeNotification = NotificationCenter.default.addObserver(
forName: .NEVPNStatusDidChange,
object: nil,
queue: .main
) { status in
print("VPN Status: \(status)")
}
observers.append(statusDidChangeNotification)
}
}
@@ -0,0 +1,4 @@
import XCTest
@testable import Services
final class TunnelsTests: XCTestCase {}
+8
View File
@@ -0,0 +1,8 @@
.DS_Store
/.build
/Packages
xcuserdata/
DerivedData/
.swiftpm/configuration/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc
+33
View File
@@ -0,0 +1,33 @@
// swift-tools-version: 5.9
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "Settings",
defaultLocalization: "en",
platforms: [
.iOS(.v16)
],
products: [
.library(
name: "Settings",
targets: ["Settings"]
)
],
dependencies: [
.package(path: "../UIComponents")
],
targets: [
.target(
name: "Settings",
dependencies: [
"UIComponents"
]
),
.testTarget(
name: "SettingsTests",
dependencies: ["Settings"]
)
]
)
@@ -0,0 +1,19 @@
import SwiftUI
import AppSettings
struct SettingsFlowCoordinator<Content: View>: View {
@ObservedObject var state: SettingsFlowState
let content: () -> Content
var body: some View {
content()
.navigationDestination(for: SettingsLink.self, destination: linkDestination)
}
@ViewBuilder private func linkDestination(link: SettingsLink) -> some View {
switch link {
case .theme:
AppearanceView(viewModel: AppearanceViewModel(path: $state.path, appSettings: AppSettings.shared))
}
}
}
@@ -0,0 +1,11 @@
import SwiftUI
public class SettingsFlowState: ObservableObject {
@Published var presentedItem: SettingsLink?
@Binding var path: NavigationPath
public init(path: Binding<NavigationPath>) {
_path = path
}
}
@@ -0,0 +1,9 @@
import Foundation
enum SettingsLink: Hashable, Identifiable {
case theme
var id: String {
String(describing: self)
}
}
@@ -0,0 +1,55 @@
import SwiftUI
import AppSettings
import Modifiers
import UIComponents
import Theme
public struct SettingsView: View {
@StateObject private var viewModel: SettingsViewModel
public init(viewModel: SettingsViewModel) {
_viewModel = StateObject(wrappedValue: viewModel)
}
public var body: some View {
SettingsFlowCoordinator(state: viewModel, content: content)
}
}
private extension SettingsView {
@ViewBuilder
func content() -> some View {
VStack {
navbar()
settingsList()
Spacer()
}
.appearanceUpdate()
.navigationBarBackButtonHidden(true)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.ignoresSafeArea(edges: [.bottom])
.background {
NymColor.background
.ignoresSafeArea()
}
}
@ViewBuilder
func navbar() -> some View {
CustomNavBar(
title: viewModel.settingsTitle,
leftButton: CustomNavBarButton(type: .back, action: { viewModel.navigateHome() })
)
}
@ViewBuilder
func settingsList() -> some View {
SettingsList(
viewModel:
SettingsListViewModel(
sections: viewModel.sections,
appVersion: viewModel.appVersion()
)
)
}
}
@@ -0,0 +1,113 @@
import Foundation
import AppVersionProvider
import UIComponents
public class SettingsViewModel: SettingsFlowState {
let settingsTitle = "settings".localizedString
var sections: [SettingsSection] {
[
connectionSection(),
themeSection(),
logsSection(),
feedbackSection(),
legalSection()
]
}
func navigateHome() {
path = .init()
}
func appVersion() -> String {
AppVersionProvider.appVersion()
}
}
private extension SettingsViewModel {
func navigateToTheme() {
path.append(SettingsLink.theme)
}
}
private extension SettingsViewModel {
func connectionSection() -> SettingsSection {
.connection(
viewModels: [
SettingsListItemViewModel(
accessory: .arrow,
title: "autoConnectTitle".localizedString,
subtitle: "autoConnectSubtitle".localizedString,
imageName: "autoConnect",
action: {}
),
SettingsListItemViewModel(
accessory: .toggle,
title: "entryLocationTitle".localizedString,
subtitle: "entryLocationSubtitle".localizedString,
imageName: "entryHop",
action: {}
)
]
)
}
func themeSection() -> SettingsSection {
.theme(
viewModels: [
SettingsListItemViewModel(
accessory: .arrow,
title: "displayTheme".localizedString,
imageName: "displayTheme",
action: { [weak self] in
self?.navigateToTheme()
}
)
]
)
}
func logsSection() -> SettingsSection {
.logs(
viewModels: [
SettingsListItemViewModel(
accessory: .arrow,
title: "logs".localizedString,
imageName: "logs",
action: {}
)
]
)
}
func feedbackSection() -> SettingsSection {
.feedback(
viewModels: [
SettingsListItemViewModel(
accessory: .arrow,
title: "feedback".localizedString,
imageName: "feedback",
action: {}
),
SettingsListItemViewModel(
accessory: .arrow,
title: "support".localizedString,
imageName: "support",
action: {}
)
]
)
}
func legalSection() -> SettingsSection {
.legal(
viewModels: [
SettingsListItemViewModel(
accessory: .arrow,
title: "legal".localizedString,
action: {}
)
]
)
}
}
@@ -0,0 +1,57 @@
import SwiftUI
import AppSettings
import Modifiers
import Theme
import UIComponents
public struct AppearanceView: View {
private let viewModel: AppearanceViewModel
public init(viewModel: AppearanceViewModel) {
self.viewModel = viewModel
}
public var body: some View {
VStack {
navbar()
themeOptions()
Spacer()
}
.appearanceUpdate()
.navigationBarBackButtonHidden(true)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.ignoresSafeArea(edges: [.bottom])
.background {
NymColor.background
.ignoresSafeArea()
}
}
}
private extension AppearanceView {
@ViewBuilder
func navbar() -> some View {
CustomNavBar(
title: viewModel.title,
leftButton: CustomNavBarButton(type: .back, action: { viewModel.navigateBack() })
)
}
@ViewBuilder
func themeOptions() -> some View {
ForEach(viewModel.themes, id: \.self) { theme in
SettingButton(
viewModel:
SettingButtonViewModel(
title: viewModel.themeTitle(for: theme),
subtitle: viewModel.themeSubtitle(for: theme),
isSelected: viewModel.isSelected(for: theme)
)
)
.onTapGesture {
viewModel.setCurrentTheme(with: theme)
}
.padding(EdgeInsets(top: 24, leading: 16, bottom: 0, trailing: 16))
}
}
}
@@ -0,0 +1,60 @@
import SwiftUI
import AppSettings
import Theme
public struct AppearanceViewModel {
private let appSettings: AppSettings
let title = "displayTheme".localizedString
@Binding var path: NavigationPath
var themes: [AppSetting.Appearance] {
AppSetting.Appearance.allCases
}
var currentTheme: AppSetting.Appearance {
appSettings.currentTheme
}
public init(path: Binding<NavigationPath>, appSettings: AppSettings) {
_path = path
self.appSettings = appSettings
}
func setCurrentTheme(with theme: AppSetting.Appearance) {
appSettings.currentTheme = theme
}
}
extension AppearanceViewModel {
func themeTitle(for theme: AppSetting.Appearance) -> String {
switch theme {
case .light:
return "lightThemeTitle".localizedString
case .dark:
return "darkThemeTitle".localizedString
case .automatic:
return "automaticThemeTitle".localizedString
}
}
func themeSubtitle(for theme: AppSetting.Appearance) -> String? {
switch theme {
case .light, .dark:
return nil
case .automatic:
return "automaticThemeSubtitle".localizedString
}
}
func isSelected(for theme: AppSetting.Appearance) -> Bool {
appSettings.currentTheme == theme
}
}
extension AppearanceViewModel {
func navigateBack() {
path.removeLast()
}
}
@@ -0,0 +1,4 @@
import XCTest
@testable import Settings
final class SettingsTests: XCTestCase {}
+8
View File
@@ -0,0 +1,8 @@
.DS_Store
/.build
/Packages
xcuserdata/
DerivedData/
.swiftpm/configuration/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
+35
View File
@@ -0,0 +1,35 @@
// swift-tools-version: 5.9
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "Theme",
defaultLocalization: "en",
platforms: [
.iOS(.v16)
],
products: [
.library(
name: "Theme",
targets: ["Theme"]
)
],
targets: [
.target(
name: "Theme",
resources: [
.copy("Resources/Fonts/Lato-Bold.ttf"),
.copy("Resources/Fonts/Lato-Regular.ttf"),
.copy("Resources/Fonts/Lato-SemiBold.ttf"),
.copy("Resources/Fonts/Lato-Medium.ttf"),
.process("Resources/Colors.xcassets"),
.process("Resources/Localizable.xcstrings")
]
),
.testTarget(
name: "ThemeTests",
dependencies: ["Theme"]
)
]
)
@@ -0,0 +1,40 @@
import SwiftUI
public struct NymColor {
// MARK: - Navigation bar -
public static let navigationBarBackground = Color(.navigationBarBackground)
public static let navigationBarSettingsGear = Color(.navigationBarSettingsGear)
// MARK: - Status button -
public static let statusButtonBackground = Color(.statusButtonBackground)
public static let sysOnSurfaceWhite = Color(.sysOnSurfaceWhite)
public static let statusInfoText = Color(.statusInfoText)
public static let statusTimer = Color(.statusTimer)
public static let countrySelectionSelectedBackground = Color(.countrySelectionSelectedBackground)
// MARK: - Network button -
public static let networkButtonCircle = Color(.networkButtonCircle)
// MARK: - Connect -
public static let connectTitle = Color(.connectTitle)
// MARK: - Background
public static let background = Color(.background)
// MARK: - Sys -
public static let sysOnSecondary = Color(.sysOnSecondary)
public static let sysOnSurface = Color(.sysOnSurface)
public static let sysOutline = Color(.sysOutline)
// MARK: - Key -
public static let confirm = Color(.confirm)
public static let statusGreen = Color(.statusGreen)
// MARK: - Primary -
public static let primaryOrange = Color(.primaryOrange)
// MARK: - Settings -
public static let settingsSeparator = Color(.settingsSeparator)
public static let settingsVersion = Color(.settingsVersion)
}
@@ -0,0 +1,7 @@
import Foundation
extension Bundle {
func localizedString(forKey key: String) -> String {
localizedString(forKey: key, value: nil, table: nil)
}
}
@@ -0,0 +1,11 @@
import SwiftUI
extension Color {
init(_ name: String) {
guard let namedColor = UIColor(named: name, in: Bundle.module, compatibleWith: nil)
else {
fatalError("Could not load color from Theme module")
}
self.init(namedColor)
}
}
@@ -0,0 +1,7 @@
import Foundation
public extension String {
var localizedString: String {
Bundle.module.localizedString(forKey: self)
}
}
@@ -0,0 +1,35 @@
import SwiftUI
public enum NymFont {
case lato(size: CGFloat, weight: LatoWeight)
public var font: Font {
switch self {
case let .lato(size, weight):
Font.custom("Lato-\(weight.rawValue)", size: size)
}
}
}
// MARK: - Weights -
extension NymFont {
public enum LatoWeight: String, CaseIterable {
case regular = "Regular"
case bold = "Bold"
case semibold = "SemiBold"
case medium = "Medium"
}
}
// MARK: - Register fonts -
extension NymFont {
public static func register() {
for latoWeight in NymFont.LatoWeight.allCases {
let fontName = "Lato-\(latoWeight.rawValue)"
guard let fontURL = Bundle.module.url(forResource: fontName, withExtension: "ttf") else { continue }
CTFontManagerRegisterFontsForURL(fontURL as CFURL, .process, nil)
}
}
}
@@ -0,0 +1,73 @@
import SwiftUI
public struct NymTextStyle {
let nymFont: NymFont
let lineSpacing: CGFloat
let kerning: CGFloat
init(nymFont: NymFont, lineSpacing: CGFloat = 0, kerning: CGFloat = 0) {
self.nymFont = nymFont
self.lineSpacing = lineSpacing
self.kerning = kerning
}
}
// MARK: - Styles -
extension NymTextStyle {
// MARK: - Title -
public struct Title {
public struct Large {
public static var primary: NymTextStyle {
NymTextStyle(nymFont: .lato(size: 22, weight: .regular))
}
}
public struct Medium {
public static var primary: NymTextStyle {
NymTextStyle(nymFont: .lato(size: 16, weight: .semibold), kerning: 0.15)
}
}
}
// MARK: - Label -
public struct Label {
public struct Huge {
public static var primary: NymTextStyle {
NymTextStyle(nymFont: .lato(size: 18, weight: .bold))
}
}
public struct Large {
public static var primary: NymTextStyle {
NymTextStyle(nymFont: .lato(size: 14, weight: .bold), kerning: 0.1)
}
}
public struct Small {
public static var primary: NymTextStyle {
NymTextStyle(nymFont: .lato(size: 11, weight: .medium), kerning: 0.5)
}
}
}
// MARK: - Body -
public struct Body {
public struct Large {
public static var primary: NymTextStyle {
NymTextStyle(nymFont: .lato(size: 16, weight: .semibold), kerning: 0.5)
}
}
public struct Medium {
public static var primary: NymTextStyle {
NymTextStyle(nymFont: .lato(size: 14, weight: .regular), kerning: 0.25)
}
}
public struct Small {
public static var primary: NymTextStyle {
NymTextStyle(nymFont: .lato(size: 12, weight: .regular), kerning: 0.4)
}
}
}
}
@@ -0,0 +1,22 @@
import SwiftUI
public struct NymTextStyleModifier: ViewModifier {
public let textStyle: NymTextStyle
public init(textStyle: NymTextStyle) {
self.textStyle = textStyle
}
public func body(content: Content) -> some View {
content
.font(textStyle.nymFont.font)
.kerning(textStyle.kerning)
.lineSpacing(textStyle.lineSpacing)
}
}
public extension View {
func textStyle(_ textStyle: NymTextStyle) -> some View {
modifier(NymTextStyleModifier(textStyle: textStyle))
}
}
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "97",
"green" : "199",
"red" : "43"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "0.100",
"blue" : "93",
"green" : "196",
"red" : "71"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "0.080",
"blue" : "0x71",
"green" : "0x5B",
"red" : "0x61"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "0.160",
"blue" : "0x33",
"green" : "0x30",
"red" : "0x30"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "113",
"green" : "101",
"red" : "105"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "153",
"green" : "143",
"red" : "147"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "display-p3",
"components" : {
"alpha" : "1.000",
"blue" : "31",
"green" : "28",
"red" : "28"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "display-p3",
"components" : {
"alpha" : "1.000",
"blue" : "255",
"green" : "250",
"red" : "254"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "display-p3",
"components" : {
"alpha" : "1.000",
"blue" : "0.639",
"green" : "0.639",
"red" : "0.639"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "display-p3",
"components" : {
"alpha" : "1.000",
"blue" : "0.349",
"green" : "0.329",
"red" : "0.337"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "31",
"green" : "27",
"red" : "28"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "229",
"green" : "225",
"red" : "230"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "31",
"green" : "27",
"red" : "28"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "1.000",
"green" : "1.000",
"red" : "1.000"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "126",
"green" : "116",
"red" : "121"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "153",
"green" : "143",
"red" : "147"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "display-p3",
"components" : {
"alpha" : "1.000",
"blue" : "0.965",
"green" : "0.957",
"red" : "0.949"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "display-p3",
"components" : {
"alpha" : "1.000",
"blue" : "31",
"green" : "27",
"red" : "28"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "255",
"green" : "255",
"red" : "255"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "31",
"green" : "27",
"red" : "28"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "0.120",
"blue" : "0x4F",
"green" : "0x45",
"red" : "0x49"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "0.160",
"blue" : "0xD0",
"green" : "0xC4",
"red" : "0xCA"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "display-p3",
"components" : {
"alpha" : "1.000",
"blue" : "1.000",
"green" : "1.000",
"red" : "1.000"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "display-p3",
"components" : {
"alpha" : "1.000",
"blue" : "0.192",
"green" : "0.157",
"red" : "0.165"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "display-p3",
"components" : {
"alpha" : "1.000",
"blue" : "0.122",
"green" : "0.106",
"red" : "0.110"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "display-p3",
"components" : {
"alpha" : "1.000",
"blue" : "0.812",
"green" : "0.769",
"red" : "0.788"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "126",
"green" : "116",
"red" : "121"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "display-p3",
"components" : {
"alpha" : "1.000",
"blue" : "208",
"green" : "196",
"red" : "202"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "78",
"green" : "110",
"red" : "251"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "229",
"green" : "225",
"red" : "230"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "79",
"green" : "69",
"red" : "73"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "126",
"green" : "116",
"red" : "121"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "113",
"green" : "91",
"red" : "98"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,391 @@
{
"sourceLanguage" : "en",
"strings" : {
"2hopWireGuardSubtitle" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Best for browsing, streaming, sharing"
}
}
}
},
"2hopWireGuardTitle" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "2-hop WireGuard"
}
}
}
},
"5hopMixnetSubtitle" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Best for payments, emails, messages"
}
}
}
},
"5hopMixnetTitle" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "5-hop mixnet"
}
}
}
},
"autoConnectSubtitle" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Auto connect at app startup"
}
}
}
},
"autoConnectTitle" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Auto-connect"
}
}
}
},
"automaticThemeSubtitle" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Use device theme"
}
}
}
},
"automaticThemeTitle" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Automatic"
}
}
}
},
"connect" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Connect"
}
}
}
},
"connected" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Connected"
}
}
}
},
"connecting" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Connecting..."
}
}
}
},
"connectTo" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Connect to"
}
}
}
},
"darkThemeTitle" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Dark theme"
}
}
}
},
"disconnected" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Disconnected"
}
}
}
},
"disconnecting" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Disconnecting..."
}
}
}
},
"displayTheme" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Display theme"
}
}
}
},
"entryLocationSubtitle" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Manually select your entry hop"
}
}
}
},
"entryLocationTitle" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Entry location selector"
}
}
}
},
"fastest" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Fastest"
}
}
}
},
"feedback" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Feedback"
}
}
}
},
"firstHop" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "First hop"
}
}
}
},
"firstHopSelection" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "First hop selection"
}
}
}
},
"lastHop" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Last hop"
}
}
}
},
"lastHopSelection" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Last hop selection"
}
}
}
},
"legal" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Legal"
}
}
}
},
"lightThemeTitle" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Light theme"
}
}
}
},
"logs" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Logs"
}
}
}
},
"NymVPN" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "NymVPN"
}
}
}
},
"search" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Search"
}
}
}
},
"searchCountry" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Search country"
}
}
}
},
"selected" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Selected"
}
}
}
},
"selectNetwork" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Select network"
}
}
}
},
"settings" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Settings"
}
}
}
},
"support" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Support"
}
}
}
},
"version" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Version"
}
}
}
}
},
"version" : "1.0"
}
@@ -0,0 +1,7 @@
import Foundation
public struct ThemeConfiguration {
public static func setup() {
NymFont.register()
}
}
@@ -0,0 +1,4 @@
import XCTest
@testable import Theme
final class ThemeTests: XCTestCase {}
+8
View File
@@ -0,0 +1,8 @@
.DS_Store
/.build
/Packages
xcuserdata/
DerivedData/
.swiftpm/configuration/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc
+36
View File
@@ -0,0 +1,36 @@
// swift-tools-version: 5.9
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "UIComponents",
defaultLocalization: "en",
platforms: [
.iOS(.v16)
],
products: [
.library(
name: "UIComponents",
targets: ["UIComponents"]
)
],
dependencies: [
.package(path: "../Theme")
],
targets: [
.target(
name: "UIComponents",
dependencies: [
"Theme"
],
resources: [
.process("Resources/Assets.xcassets")
]
),
.testTarget(
name: "UIComponentsTests",
dependencies: ["UIComponents"]
)
]
)

Some files were not shown because too many files have changed in this diff Show More