├── .gitignore
├── LICENSE
├── README.md
├── balena.yml
├── captive-portal
├── Dockerfile.template
├── captive-portal.py
├── redirect.py
└── start.sh
├── docker-compose.yml
├── ipfs
├── Dockerfile
└── config_local_access
├── logo.png
└── wifi-repeater
├── Dockerfile.template
├── package-lock.json
├── package.json
├── src
├── dbus.ts
├── index.ts
├── nm.ts
└── types
│ └── index.ts
├── tsconfig.json
└── typings
└── dbus-native.d.ts
/.gitignore:
--------------------------------------------------------------------------------
1 | # File created using '.gitignore Generator' for Visual Studio Code: https://bit.ly/vscode-gig
2 |
3 | # Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode,linux
4 | # Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode,linux
5 |
6 | ### Balena ###
7 | .balena
8 | balena-router/build
9 |
10 | ### Linux ###
11 | *~
12 |
13 | # temporary files which can be created if a process still has a handle open of a deleted file
14 | .fuse_hidden*
15 |
16 | # KDE directory preferences
17 | .directory
18 |
19 | # Linux trash folder which might appear on any partition or disk
20 | .Trash-*
21 |
22 | # .nfs files are created when an open file is removed but is still being accessed
23 | .nfs*
24 |
25 | ### VisualStudioCode ###
26 | .vscode/*
27 | !.vscode/settings.json
28 | !.vscode/tasks.json
29 | !.vscode/launch.json
30 | !.vscode/extensions.json
31 | !.vscode/*.code-snippets
32 |
33 | # Local History for Visual Studio Code
34 | .history/
35 |
36 | # Built Visual Studio Code Extensions
37 | *.vsix
38 |
39 | ### VisualStudioCode Patch ###
40 | # Ignore all local history of files
41 | .history
42 | .ionide
43 |
44 | # Support for Project snippet scope
45 |
46 | # End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,linux
47 |
48 | # Custom rules (everything added below won't be overriden by 'Generate .gitignore File' if you use 'Update' option)
49 |
50 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 coolabnet
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # P2P Hotspot
2 |
3 | [](https://dashboard.balena-cloud.com/deploy?repoUrl=https://github.com/coolabnet/hotspot)
4 |
5 |
6 | P2P Hotspot is a low-energy device that creates an access-point serving p2p applications, bridges and content. Software and content are automatically updated while online.
7 |
8 | A team can easily monitor and give direct support to all devices (with granted consent) while online.
9 |
10 | ## Getting started
11 |
12 | We'll need Internet connection for downloading latest software and content. But after initial setup, device can exist and be updated offline.
13 |
14 | *Needed hardware:*
15 |
16 | - Computer: Raspberry Pi 3 or 4
17 | - Storage: class 10 micro sd with min 16Gb or SSD connected thru usb
18 | - (if using SSD && acess-point) Wifi Dongle: a Wifi dongle compatible with the device
19 | - Internet connection through ethernet cable
20 |
21 | *Setup*
22 |
23 | 1. Click "[Deploy with Balena](https://www.balena.io/docs/learn/deploy/deploy-with-balena-button/)" button
24 | 1. Create free Balena account
25 | 1. Set community environmental variables (wifi acess point name, wifi password, device name on network, community name, anguage, content repository address, map-tiles links)
26 | 1. Download project image
27 | 1. Burn image to micro sd or usb SSD
28 | 1. Plug storage into device
29 | 1. Plug device to power supply
30 | 1. Connect to the Internet thru an Ethernet cable
31 | 1. Check Balena dashboard if device is online
32 | 1. Wait for device to update it's software on Balena dashboard
33 | 1. Set environment variables (or use defaults)
34 |
35 | | Env var | Description | Default |
36 | | ------------- | ------------- | ------------- |
37 | | AP_SSID | Access point network name. | `WiFi Repeater` |
38 | | AP_PASSWORD | Access point network password. | `none` |
39 | | WIFI_SSID | WiFi network name | |
40 | | WIFI_PASSWORD | WiFi network password | |
41 | | PUB_NAME | The SSB's pub name | `P2P Hotspot` |
42 | | ROOM | Room address where the pub will connnect | `room.coolab.org:8008~shs:foHx1HuvaN++iCrQS+Zi916V6iNYEOtj9ZOAo+a0E+Q=` |
43 | | PUB | Pub address where the pub will connect | `pub.freesocial.co:8008~shs:ofYKOy2p9wsaxV73GqgOyh6C6nRGFM5FyciQyxwBd6A=` |
44 |
45 |
46 | ## Usage
47 |
48 | 1. On a phone or computer search for WiFi access point and connect
49 | 1. Captive-portal with portal should pop-up, if not go to [10.42.0.1:8081](http://10.42.0.1:8081)
50 | 1. Follow guides and install tools on user devices
51 |
52 | ## Connectivity
53 | 
54 |
55 | *Uses [wifi-repeater](https://github.com/balenalabs/wifi-repeater)*
56 |
57 | Device will automatically scan your network and check each device's capabilities. It will then attempt to configure the devices to work in `Access Point` mode and if that is not possible it will switch to `Repeater` mode. You *do not* need to pre configure the device to work in either mode.
58 |
59 | For `Access Point` mode it needs:
60 | - Internet connectivity via Ethernet
61 | - Wireless device with AP capabilities (such as the onboard WiFi chip in Raspberry Pi devices)
62 |
63 | For `Repeater` mode it needs:
64 | - Wireless device with AP capabilities (such as the onboard WiFi chip in Raspberry Pi devices)
65 | - Secondary wireless device and valid credentials for a wireless network.
66 |
67 | In case you have no access to an ethernet cable connecting to the routing device, WiFi connection can be set directly inside the SD card by following [this guide](https://www.balena.io/docs/reference/OS/network/#wifi-setup).
68 | ### LED patterns
69 |
70 | In case something goes wrong WiFi repeater will produce a series of blinking patterns with the ACT LED (next to PWR LED) to help troubleshoot the issue.
71 | Valid patterns are the following:
72 |
73 | | LED pattern | Problem | Description | Solution (AP mode) | Solution (Repeater mode) |
74 | | ------------- | ------------- | ------------- | ------------- | ------------- |
75 | | 2 blinks | Could not find a wireless device with Access Point capabilities | Wireless devices detected don't support Access Point mode. | Use a WiFi chipset that supports AP mode or a WiFi dongle. | Use a WiFi chipset that supports AP mode or a WiFi dongle. |
76 | | 3 blinks | Could not find a secondary wireless device | Ethernet is disconnected or has no internet access. Switched to repeater mode but could not find a secondary wireless device. | Provide internet access via Ethernet cable. | Provide a secondary wireless device by using a WiFi dongle. |
77 | | 4 blinks | WiFi credentials for secondary wireless device not provided. | Ethernet is disconnected or has no internet access. Switched to repeater mode but could not find WiFi credentials. | Provide internet access via Ethernet cable. | Provide valid WiFi credentials |
78 | | 5 blinks | No internet access | Ethernet is disconnected or has no internet access. Switched to repeater mode, connected to WiFi but still have no internet access. | Provide internet access via Ethernet cable. | Ensure the target WiFi has internet access. |
79 | ## Software stack
80 |
81 | ### Operating System
82 |
83 | Meant to run on [Balena](https://balena.io), which enables easily composing service blocks on a performant container-based operating system. Advantages:
84 |
85 | - [Balena Hub](https://hub.balena.io/) has a growing number of out-of-the-box apps
86 | - [Balena Cloud](https://balena-cloud.com/) is free as long as project is published and used from Balena Hub
87 | - simple migration from existing docker/docker-compose stack
88 | - lightweight container-based operating system
89 | - support for various types of single-board-computers and architectures
90 | - over-the-air-updates
91 | - small self-updating images
92 | - per-device release pinning
93 | - bulk monitoring of all devices and their services
94 | - remote access to all devices and services via web terminal
95 | - ssh tunnel and remote support
96 |
97 | ### Service Stack
98 |
99 | - **Wifi**: creates a WiFi Access-Point with a Captive-portal redirecting user to portal application
100 | - **Portal**: application which onboards new users and links to content and services
101 | - **SSB-Pub**: [Secure Scuttlebut's](https://scuttlebutt.nz/) distributed database that connects automatically to a room and pub outside the local network (on the Internet)
102 | - **Distributed File System**: automatically updates content if online through p2p sync using [IPFS](https://ipfs.io/)
103 | ## Extending
104 | - Support more simultaneous users and extend wifi range with a router
105 | - Extend router's wifi range by setting up a mesh network with your neighbors
106 |
107 | ## TODO
108 |
109 | - Make it work offline
110 | - Portal work on port 80
111 | - Check for portal and installer updates before attempting to download them
112 | - Have Internet block work
113 | - Optionally password protect Internet access
114 | - Add p2p installer and browser applications ([Dat Installer](https://github.com/staltz/dat-installer) and [Agregore Browser](https://agregore.mauve.moe/) for example)
115 | - Run an [F-Droid repository](https://gitlab.com/fdroid/wiki/-/wikis/List-of-F-Droid-repositories)
116 | - Add service that scrapes latest apks and installers and serves them as F-droid and Dat-Installer repositories
117 | - Have the pub publish an about photo
118 | - Integrate [Peach Cloud](http://peachcloud.org/)
119 | - Create an ARM32 version (NodeJS ssb pub)
120 | - Add dnsmasq to WiFi and configure proxy for custom domains
121 |
--------------------------------------------------------------------------------
/balena.yml:
--------------------------------------------------------------------------------
1 | name: P2P Hotpost
2 | type: sw.application
3 | description: >-
4 | P2P Hotspot is a low-energy device that creates an access-point serving p2p applications, bridges and content.
5 | Software stack and content are automatically updated while online.
6 | assets:
7 | repository:
8 | type: blob.asset
9 | data:
10 | url: 'https://github.com/coolabnet/hotspot'
11 | logo:
12 | type: blob.asset
13 | data:
14 | url: >-
15 | https://raw.githubusercontent.com/coolabnet/hotspot/main/logo.png
16 | data:
17 | applicationEnvironmentVariables:
18 | - AP_SSID: "P2P Hotspot"
19 | - AP_PASSWORD: ""
20 | - TZ: America/Brasil
21 | - SET_HOSTNAME: p2p
22 | - PUB_NAME: "P2P Hotspot"
23 | - ROOM: "net:room.coolab.org:8008~shs:foHx1HuvaN++iCrQS+Zi916V6iNYEOtj9ZOAo+a0E+Q="
24 | - PUB: "net:pub.freesocial.co:8008~shs:ofYKOy2p9wsaxV73GqgOyh6C6nRGFM5FyciQyxwBd6A="
25 | defaultDeviceType: raspberry-pi
26 | supportedDeviceTypes:
27 | - raspberrypi4-64
28 | - raspberrypi3-64
29 | version: 1.0.2
30 |
31 |
32 |
--------------------------------------------------------------------------------
/captive-portal/Dockerfile.template:
--------------------------------------------------------------------------------
1 | # Build stage
2 | FROM balenalib/%%BALENA_MACHINE_NAME%%-debian
3 |
4 | RUN install_packages python dbus jq python wget iptables unzip
5 |
6 | ENV DBUS_SYSTEM_BUS_ADDRESS=unix:path=/host/run/dbus/system_bus_socket
7 |
8 | RUN update-alternatives --set iptables /usr/sbin/iptables-legacy && \
9 | update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy
10 |
11 | WORKDIR /usr/src
12 | COPY . .
13 |
14 | CMD ["bash", "start.sh"]
15 |
--------------------------------------------------------------------------------
/captive-portal/captive-portal.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | #adapted from https://github.com/nikosft/captive-portal/
3 | import subprocess
4 | from SimpleHTTPServer import SimpleHTTPRequestHandler
5 | from SocketServer import TCPServer
6 | import cgi
7 | import os
8 |
9 | # These variables are used as settings
10 | RE_PORT = 9090
11 | PORT = 8081 # the port in which the captive portal web server listens
12 | IFACE = "wlan0" # the interface that captive portal protects
13 | FDROID_IP_1 = "148.251.140.42"
14 | FDROID_IP_2 = "149.202.95.241"
15 |
16 | ip_pipe = subprocess.Popen(['ip', 'r'], stdout=subprocess.PIPE)
17 | interface_pipe = subprocess.Popen (['grep', IFACE], stdin=ip_pipe.stdout, stdout=subprocess.PIPE)
18 | IP_ADDRESS = subprocess.check_output (['cut', '-d', ' ', '-f', '9'], stdin=interface_pipe.stdout).split()[0]
19 |
20 |
21 | '''
22 | This it the http server used by the the captive portal
23 | '''
24 | class CaptivePortal(SimpleHTTPRequestHandler):
25 | '''
26 | https://stackoverflow.com/questions/16583827/cors-with-python-basehttpserver-501-unsupported-method-options-in-chrome
27 | '''
28 | def do_OPTIONS(self):
29 | self.send_response(200, "ok")
30 | self.send_header('Access-Control-Allow-Origin', '*')
31 | self.send_header('Access-Control-Allow-Methods', 'GET, OPTIONS')
32 | self.send_header("Access-Control-Allow-Headers", "X-Requested-With")
33 | self.send_header("Access-Control-Allow-Headers", "Content-Type")
34 | self.end_headers()
35 | '''
36 | this is called when the user submits the login form
37 | '''
38 | def do_POST(self):
39 | self.send_response(200)
40 | self.send_header("Content-Type", "application/json")
41 | self.send_header('Access-Control-Allow-Origin', '*')
42 | self.send_header('Access-Control-Allow-Headers', 'Authorization, Content-Type')
43 | self.send_header('Access-Control-Allow-Methods', 'POST')
44 | self.end_headers()
45 | form = cgi.FieldStorage(
46 | fp=self.rfile,
47 | headers=self.headers,
48 | environ={'REQUEST_METHOD':'POST',
49 | 'CONTENT_TYPE':self.headers['Content-Type'],
50 | })
51 | # username = form.getvalue("username")
52 | # password = form.getvalue("password")
53 | #dummy security check
54 | # if username == 'nikos' and password == 'fotiou':
55 | #authorized user
56 | remote_IP = self.client_address[0]
57 | print ('New authorization from '+ remote_IP)
58 | print ('Updating IP tables')
59 | subprocess.call(["iptables","-t", "nat", "-I", "PREROUTING","1", "-s", remote_IP, "-j" ,"ACCEPT"])
60 | subprocess.call(["iptables", "-I", "FORWARD", "-s", remote_IP, "-j" ,"ACCEPT"])
61 | self.wfile.write("{success: true, ip:" + remote_IP + "}")
62 | # else:
63 | # #show the login form
64 | # self.wfile.write(self.html_login)
65 |
66 | #the following function makes server produce no output
67 | #comment it out if you want to print diagnostic messages
68 | #def log_message(self, format, *args):
69 | # return
70 |
71 | print "*********************************************"
72 | print "* Note, if there are already iptables rules *"
73 | print "* this script may not work. Flush iptables *"
74 | print "* at your own risk using iptables -F *"
75 | print "*********************************************"
76 | print "Updating iptables"
77 | print ".. Allow TCP DNS"
78 | subprocess.call(["iptables", "-A", "FORWARD", "-i", IFACE, "-p", "tcp", "--dport", "53", "-j" ,"ACCEPT"])
79 | print ".. Allow UDP DNS"
80 | subprocess.call(["iptables", "-A", "FORWARD", "-i", IFACE, "-p", "udp", "--dport", "53", "-j" ,"ACCEPT"])
81 | print ".. Allow traffic to captive portal"
82 | subprocess.call(["iptables", "-A", "FORWARD", "-i", IFACE, "-p", "tcp", "--dport", str(PORT),"-d", IP_ADDRESS, "-j" ,"ACCEPT"])
83 | subprocess.call(["iptables", "-A", "FORWARD", "-i", IFACE, "-p", "tcp", "--dport", str(RE_PORT),"-d", IP_ADDRESS, "-j" ,"ACCEPT"])
84 | print ".. Allow traffic to F-Droid repositories"
85 | subprocess.call(["iptables", "-A", "FORWARD", "-i", IFACE, "-p", "tcp", "--dport", "80","-d", FDROID_IP_1, "-j" ,"ACCEPT"])
86 | subprocess.call(["iptables", "-A", "FORWARD", "-i", IFACE, "-p", "tcp", "--dport", "80","-d", FDROID_IP_2, "-j" ,"ACCEPT"])
87 | print ".. Block all other traffic"
88 | subprocess.call(["iptables", "-A", "FORWARD", "-i", IFACE, "-j" ,"DROP"])
89 | print "Starting web server"
90 | httpd = TCPServer(('', PORT), CaptivePortal)
91 | print 'started httpserver on'+str(PORT)
92 | print "Redirecting HTTP traffic to captive portal"
93 | subprocess.call(["iptables", "-t", "nat", "-A", "PREROUTING", "-i", IFACE, "-p", "tcp", "--dport", "80", "-j" ,"DNAT", "--to-destination", IP_ADDRESS+":"+str(RE_PORT)])
94 |
95 |
96 | try:
97 | httpd.serve_forever()
98 | except KeyboardInterrupt:
99 | pass
100 | httpd.server_close()
101 |
--------------------------------------------------------------------------------
/captive-portal/redirect.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
4 | import subprocess
5 |
6 | RE_PORT = 9090
7 | PORT = 8081
8 | IFACE = "wlan0" # the interface that captive portal protects
9 |
10 | ip_pipe = subprocess.Popen(['ip', 'r'], stdout=subprocess.PIPE)
11 | interface_pipe = subprocess.Popen (['grep', IFACE], stdin=ip_pipe.stdout, stdout=subprocess.PIPE)
12 | IP_ADDRESS = subprocess.check_output (['cut', '-d', ' ', '-f', '9'], stdin=interface_pipe.stdout).split()[0]
13 | '''
14 | Redirect
15 | '''
16 | class Redirect(BaseHTTPRequestHandler):
17 | #this is the index of the captive portal
18 | #it simply redirects the user to the to login page
19 | html_redirect = """
20 |
21 |
22 |
23 |
24 |
25 | Redirecting to portal
26 |
27 |
28 | """%(IP_ADDRESS, PORT)
29 |
30 | '''
31 | if the user requests the login page show it, else
32 | use the redirect page
33 | '''
34 | def do_GET(self):
35 | path = self.path
36 | self.send_response(200)
37 | self.send_header("Content-type", "text/html")
38 | self.end_headers()
39 | self.wfile.write(self.html_redirect)
40 |
41 | httpdRe = HTTPServer(('', RE_PORT), Redirect)
42 | print 'started httpserver on'+str(RE_PORT)
43 |
44 | try:
45 | httpdRe.serve_forever()
46 | except KeyboardInterrupt:
47 | pass
48 | httpdRe.server_close()
49 |
--------------------------------------------------------------------------------
/captive-portal/start.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # portal
4 | echo 'Downloading Portal'
5 | curl -s https://api.github.com/repos/coolabnet/hotspot/releases/latest \
6 | | grep "browser_download_url.*zip" \
7 | | cut -d : -f 2,3 \
8 | | tr -d \" \
9 | | wget -qi -
10 | rm -rf /usr/src/dist
11 | unzip portal.zip
12 | mv /usr/src/dist/* /usr/src/
13 | # Apks
14 | mkdir -p /usr/src/apks
15 | wget https://f-droid.org/F-Droid.apk -O /usr/src/apks/fdroid.apk
16 | wget https://manyver.se/apk -O /usr/src/apks/manyverse.apk
17 | #
18 | # Python
19 | echo 'Starting Python server'
20 | python /usr/src/captive-portal.py &
21 | python /usr/src/redirect.py
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '2.1'
2 |
3 | volumes:
4 | media:
5 | sync-download:
6 | sync-staging:
7 | ssb-data:
8 | services:
9 | # https://github.com/balenablocks/wifi-connect
10 | wifi:
11 | build: wifi-repeater
12 | restart: always
13 | network_mode: host
14 | privileged: true
15 | labels:
16 | io.balena.features.dbus: '1'
17 | io.balena.features.firmware: '1'
18 | captive-portal:
19 | build: captive-portal
20 | restart: always
21 | network_mode: host
22 | privileged: true
23 | labels:
24 | io.balena.features.dbus: '1'
25 | depends_on:
26 | - wifi
27 |
28 | # https://github.com/balenablocks/hostname
29 | hostname:
30 | image: balenablocks/hostname
31 | restart: "no"
32 | labels:
33 | io.balena.features.supervisor-api: 1
34 | environment:
35 | SET_HOSTNAME: p2p
36 | # https://hub.docker.com/repository/docker/communityfirst/ssb-go-pub
37 | ssb:
38 | image: communityfirst/ssb-go-pub:latest
39 | network_mode: host
40 | environment:
41 | ROOM: "net:room.coolab.org:8008~shs:foHx1HuvaN++iCrQS+Zi916V6iNYEOtj9ZOAo+a0E+Q="
42 | PUB: "net:pub.freesocial.co:8008~shs:ofYKOy2p9wsaxV73GqgOyh6C6nRGFM5FyciQyxwBd6A="
43 | volumes:
44 | - 'ssb-data:/root/.ssb-go'
45 | restart: unless-stopped
46 | ipfs:
47 | build: ipfs
48 | network_mode: host
49 | volumes:
50 | - 'sync-download:/data/ipfs'
51 | - 'sync-staging:/export'
52 | - 'media:/data'
53 | labels:
54 | io.balena.features.supervisor-api: '1'
55 | depends_on:
56 | - wifi
57 |
58 |
--------------------------------------------------------------------------------
/ipfs/Dockerfile:
--------------------------------------------------------------------------------
1 | # Note: when updating the go minor version here, also update the go-channel in snap/snapcraft.yml
2 | FROM golang:1.17-buster
3 | LABEL maintainer="Steven Allen "
4 |
5 | # Install deps
6 | RUN apt-get update && apt-get install -y \
7 | git \
8 | libssl-dev \
9 | ca-certificates \
10 | fuse \
11 | jq cron
12 |
13 | ENV SRC_DIR /go-ipfs
14 |
15 |
16 | RUN cd /tmp && git clone https://github.com/ipfs/go-ipfs.git
17 | # Download packages first so they can be cached.
18 | RUN ls /tmp/
19 | RUN mkdir $SRC_DIR
20 | RUN cp /tmp/go-ipfs/go.mod $SRC_DIR/
21 | RUN cp /tmp/go-ipfs/go.sum $SRC_DIR/
22 | RUN cd $SRC_DIR \
23 | && go mod download
24 |
25 | RUN cp -r /tmp/go-ipfs/* $SRC_DIR/
26 |
27 | # Preload an in-tree but disabled-by-default plugin by adding it to the IPFS_PLUGINS variable
28 | # e.g. docker build --build-arg IPFS_PLUGINS="foo bar baz"
29 | ARG IPFS_PLUGINS
30 |
31 | # Build the thing.
32 | # Also: fix getting HEAD commit hash via git rev-parse.
33 | RUN cd $SRC_DIR \
34 | && mkdir -p .git/objects \
35 | && make build GOTAGS=openssl IPFS_PLUGINS=$IPFS_PLUGINS
36 |
37 | # Get su-exec, a very minimal tool for dropping privileges,
38 | # and tini, a very minimal init daemon for containers
39 | ENV SUEXEC_VERSION v0.2
40 | ENV TINI_VERSION v0.19.0
41 | RUN set -eux; \
42 | dpkgArch="$(dpkg --print-architecture)"; \
43 | case "${dpkgArch##*-}" in \
44 | "amd64" | "armhf" | "arm64") tiniArch="tini-static-$dpkgArch" ;;\
45 | *) echo >&2 "unsupported architecture: ${dpkgArch}"; exit 1 ;; \
46 | esac; \
47 | cd /tmp \
48 | && git clone https://github.com/ncopa/su-exec.git \
49 | && cd su-exec \
50 | && git checkout -q $SUEXEC_VERSION \
51 | && make su-exec-static \
52 | && cd /tmp \
53 | && wget -q -O tini https://github.com/krallin/tini/releases/download/$TINI_VERSION/$tiniArch \
54 | && chmod +x tini
55 |
56 | #RUN cat $SRC_DIR/start_ipfs >> $SRC_DIR/bin/container_daemon
57 | COPY config_local_access $SRC_DIR
58 | RUN head -n -1 $SRC_DIR/bin/container_daemon > $SRC_DIR/start && cat $SRC_DIR/start $SRC_DIR/config_local_access > $SRC_DIR/bin/container_daemon
59 |
60 | # Now comes the actual target image, which aims to be as small as possible.
61 | FROM busybox:1.31.1-glibc
62 | LABEL maintainer="Steven Allen "
63 |
64 | # Get the ipfs binary, entrypoint script, and TLS CAs from the build container.
65 | ENV SRC_DIR /go-ipfs
66 | COPY --from=0 $SRC_DIR/cmd/ipfs/ipfs /usr/local/bin/ipfs
67 | COPY --from=0 $SRC_DIR/bin/container_daemon /usr/local/bin/start_ipfs
68 | COPY --from=0 /tmp/su-exec/su-exec-static /sbin/su-exec
69 | COPY --from=0 /tmp/tini /sbin/tini
70 | COPY --from=0 /bin/fusermount /usr/local/bin/fusermount
71 | COPY --from=0 /etc/ssl/certs /etc/ssl/certs
72 | COPY --from=0 /usr/bin/jq /usr/local/bin/jq
73 |
74 | # Add suid bit on fusermount so it will run properly
75 | RUN chmod 4755 /usr/local/bin/fusermount
76 |
77 | # Fix permissions on start_ipfs (ignore the build machine's permissions)
78 | RUN chmod 0755 /usr/local/bin/start_ipfs
79 |
80 | # This shared lib (part of glibc) doesn't seem to be included with busybox.
81 | COPY --from=0 /lib/*-linux-gnu*/libdl.so.2 /lib/
82 |
83 | # Copy over SSL libraries.
84 | COPY --from=0 /usr/lib/*-linux-gnu*/libssl.so* /usr/lib/
85 | COPY --from=0 /usr/lib/*-linux-gnu*/libcrypto.so* /usr/lib/
86 |
87 | # Copy jq library
88 | COPY --from=0 /usr/lib/*-linux-gnu*/libjq.so* /usr/lib/
89 | COPY --from=0 /usr/lib/*-linux-gnu*/libonig.so* /usr/lib/
90 | COPY --from=0 /usr/lib/*-linux-gnu*/libc.so* /usr/lib/
91 | COPY --from=0 /usr/lib/*-linux-gnu*/libm.so* /usr/lib/
92 |
93 |
94 | # Swarm TCP; should be exposed to the public
95 | EXPOSE 4001
96 | # Swarm UDP; should be exposed to the public
97 | EXPOSE 4001/udp
98 | # Daemon API; must not be exposed publicly but to client services under you control
99 | EXPOSE 5001
100 | # Web Gateway; can be exposed publicly with a proxy, e.g. as https://ipfs.example.org
101 | EXPOSE 8080
102 | # Swarm Websockets; must be exposed publicly when the node is listening using the websocket transport (/ipX/.../tcp/8081/ws).
103 | EXPOSE 8081
104 |
105 | # Create the fs-repo directory and switch to a non-privileged user.
106 | ENV IPFS_PATH /data/ipfs
107 | RUN mkdir -p $IPFS_PATH \
108 | && adduser -D -h $IPFS_PATH -u 1000 -G users ipfs \
109 | && chown ipfs:users $IPFS_PATH
110 |
111 | # Create mount points for `ipfs mount` command
112 | RUN mkdir /ipfs /ipns \
113 | && chown ipfs:users /ipfs /ipns
114 |
115 | # Expose the fs-repo as a volume.
116 | # start_ipfs initializes an fs-repo if none is mounted.
117 | # Important this happens after the USER directive so permissions are correct.
118 | VOLUME $IPFS_PATH
119 |
120 | # The default logging level
121 | ENV IPFS_LOGGING ""
122 |
123 | # This just makes sure that:
124 | # 1. There's an fs-repo, and initializes one if there isn't.
125 | # 2. The API and Gateway are accessible from outside the container.
126 | ENTRYPOINT ["/sbin/tini", "--", "/usr/local/bin/start_ipfs"]
127 |
128 | # Heathcheck for the container
129 | # QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn is the CID of empty folder
130 | HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
131 | CMD ipfs dag stat /ipfs/QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn || exit 1
132 |
133 |
134 | # Execute the daemon subcommand by default
135 | CMD ["daemon", "--migrate=true", "--agent-version-suffix=docker"]
136 |
--------------------------------------------------------------------------------
/ipfs/config_local_access:
--------------------------------------------------------------------------------
1 | #balena ----
2 | #now let's configure it to accept local network connections
3 | IP=$(wget -qO- "$BALENA_SUPERVISOR_ADDRESS/v1/device?apikey=$BALENA_SUPERVISOR_API_KEY"| jq -r .ip_address)
4 | NAME=$(wget -qO- "$BALENA_SUPERVISOR_ADDRESS/v1/device/host-config?apikey=$BALENA_SUPERVISOR_API_KEY"| jq -r .network.hostname)
5 |
6 | J='['
7 | J_FORMAT='"http://%s:5001", '
8 |
9 | for one_ip in $IP ; do
10 | J=$J$(printf "$J_FORMAT" "$one_ip")
11 | done
12 |
13 | J_FORMAT='"http://%s.local:5001", "http://localhost:3000", "http://127.0.0.1:5001", "https://webui.ipfs.io"]'
14 | J=$J$(printf "$J_FORMAT" "$NAME")
15 |
16 |
17 | ipfs config --json API.HTTPHeaders.Access-Control-Allow-Origin "$J"
18 | ipfs config --json API.HTTPHeaders.Access-Control-Allow-Methods '["PUT", "POST"]'
19 |
20 | exec ipfs "$@"
21 |
22 |
--------------------------------------------------------------------------------
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coolabnet/hotspot/b23bff4b79a9e215cb62a6a773590aaab5750798/logo.png
--------------------------------------------------------------------------------
/wifi-repeater/Dockerfile.template:
--------------------------------------------------------------------------------
1 | # Build stage
2 | FROM balenalib/%%BALENA_MACHINE_NAME%%-node:12-build as builder
3 |
4 | WORKDIR /usr/src
5 |
6 | RUN npm install blinking
7 |
8 | # Run stage
9 | FROM balenalib/%%BALENA_MACHINE_NAME%%-node:12-run
10 | ENV DBUS_SYSTEM_BUS_ADDRESS=unix:path=/host/run/dbus/system_bus_socket
11 |
12 | RUN install_packages dbus
13 |
14 | WORKDIR /usr/src
15 | COPY . .
16 | COPY --from=builder /usr/src .
17 | RUN npm install && npm run build
18 |
19 | CMD [ "node", "/usr/src/build/index.js" ]
20 |
--------------------------------------------------------------------------------
/wifi-repeater/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "balenarepeater",
3 | "version": "0.1.0",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "@types/node": {
8 | "version": "13.9.0",
9 | "resolved": "https://registry.npmjs.org/@types/node/-/node-13.9.0.tgz",
10 | "integrity": "sha512-0ARSQootUG1RljH2HncpsY2TJBfGQIKOOi7kxzUY6z54ePu/ZD+wJA8zI2Q6v8rol2qpG/rvqsReco8zNMPvhQ==",
11 | "dev": true
12 | },
13 | "abstract-socket": {
14 | "version": "2.1.1",
15 | "resolved": "https://registry.npmjs.org/abstract-socket/-/abstract-socket-2.1.1.tgz",
16 | "integrity": "sha512-YZJizsvS1aBua5Gd01woe4zuyYBGgSMeqDOB6/ChwdTI904KP6QGtJswXl4hcqWxbz86hQBe++HWV0hF1aGUtA==",
17 | "optional": true,
18 | "requires": {
19 | "bindings": "^1.2.1",
20 | "nan": "^2.12.1"
21 | }
22 | },
23 | "bindings": {
24 | "version": "1.5.0",
25 | "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
26 | "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
27 | "optional": true,
28 | "requires": {
29 | "file-uri-to-path": "1.0.0"
30 | }
31 | },
32 | "blinking": {
33 | "version": "0.0.3",
34 | "resolved": "https://registry.npmjs.org/blinking/-/blinking-0.0.3.tgz",
35 | "integrity": "sha1-c6LX+J2z2lSzYFxJiqXYYGv8Hnc=",
36 | "requires": {
37 | "bluebird": "^3.0.0"
38 | }
39 | },
40 | "bluebird": {
41 | "version": "3.7.2",
42 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
43 | "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="
44 | },
45 | "dbus-native": {
46 | "version": "0.4.0",
47 | "resolved": "https://registry.npmjs.org/dbus-native/-/dbus-native-0.4.0.tgz",
48 | "integrity": "sha512-i3zvY3tdPEOaMgmK4riwupjDYRJ53rcE1Kj8rAgnLOFmBd0DekUih59qv8v+Oyils/U9p+s4sSsaBzHWLztI+Q==",
49 | "requires": {
50 | "abstract-socket": "^2.0.0",
51 | "event-stream": "^4.0.0",
52 | "hexy": "^0.2.10",
53 | "long": "^4.0.0",
54 | "optimist": "^0.6.1",
55 | "put": "0.0.6",
56 | "safe-buffer": "^5.1.1",
57 | "xml2js": "^0.4.17"
58 | }
59 | },
60 | "duplexer": {
61 | "version": "0.1.1",
62 | "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz",
63 | "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E="
64 | },
65 | "event-stream": {
66 | "version": "4.0.1",
67 | "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-4.0.1.tgz",
68 | "integrity": "sha512-qACXdu/9VHPBzcyhdOWR5/IahhGMf0roTeZJfzz077GwylcDd90yOHLouhmv7GJ5XzPi6ekaQWd8AvPP2nOvpA==",
69 | "requires": {
70 | "duplexer": "^0.1.1",
71 | "from": "^0.1.7",
72 | "map-stream": "0.0.7",
73 | "pause-stream": "^0.0.11",
74 | "split": "^1.0.1",
75 | "stream-combiner": "^0.2.2",
76 | "through": "^2.3.8"
77 | }
78 | },
79 | "file-uri-to-path": {
80 | "version": "1.0.0",
81 | "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
82 | "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
83 | "optional": true
84 | },
85 | "from": {
86 | "version": "0.1.7",
87 | "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz",
88 | "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4="
89 | },
90 | "hexy": {
91 | "version": "0.2.11",
92 | "resolved": "https://registry.npmjs.org/hexy/-/hexy-0.2.11.tgz",
93 | "integrity": "sha512-ciq6hFsSG/Bpt2DmrZJtv+56zpPdnq+NQ4ijEFrveKN0ZG1mhl/LdT1NQZ9se6ty1fACcI4d4vYqC9v8EYpH2A=="
94 | },
95 | "long": {
96 | "version": "4.0.0",
97 | "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
98 | "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA=="
99 | },
100 | "map-stream": {
101 | "version": "0.0.7",
102 | "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz",
103 | "integrity": "sha1-ih8HiW2CsQkmvTdEokIACfiJdKg="
104 | },
105 | "minimist": {
106 | "version": "0.0.10",
107 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz",
108 | "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8="
109 | },
110 | "nan": {
111 | "version": "2.14.0",
112 | "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz",
113 | "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==",
114 | "optional": true
115 | },
116 | "optimist": {
117 | "version": "0.6.1",
118 | "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
119 | "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=",
120 | "requires": {
121 | "minimist": "~0.0.1",
122 | "wordwrap": "~0.0.2"
123 | }
124 | },
125 | "pause-stream": {
126 | "version": "0.0.11",
127 | "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz",
128 | "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=",
129 | "requires": {
130 | "through": "~2.3"
131 | }
132 | },
133 | "put": {
134 | "version": "0.0.6",
135 | "resolved": "https://registry.npmjs.org/put/-/put-0.0.6.tgz",
136 | "integrity": "sha1-MPX2C9bkOJvTKeFqJThsuy5KAKM="
137 | },
138 | "safe-buffer": {
139 | "version": "5.2.0",
140 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
141 | "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg=="
142 | },
143 | "sax": {
144 | "version": "1.2.4",
145 | "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
146 | "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
147 | },
148 | "split": {
149 | "version": "1.0.1",
150 | "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz",
151 | "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==",
152 | "requires": {
153 | "through": "2"
154 | }
155 | },
156 | "stream-combiner": {
157 | "version": "0.2.2",
158 | "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz",
159 | "integrity": "sha1-rsjLrBd7Vrb0+kec7YwZEs7lKFg=",
160 | "requires": {
161 | "duplexer": "~0.1.1",
162 | "through": "~2.3.4"
163 | }
164 | },
165 | "through": {
166 | "version": "2.3.8",
167 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
168 | "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU="
169 | },
170 | "typescript": {
171 | "version": "3.8.3",
172 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz",
173 | "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w=="
174 | },
175 | "wordwrap": {
176 | "version": "0.0.3",
177 | "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
178 | "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc="
179 | },
180 | "xml2js": {
181 | "version": "0.4.23",
182 | "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
183 | "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==",
184 | "requires": {
185 | "sax": ">=0.6.0",
186 | "xmlbuilder": "~11.0.0"
187 | }
188 | },
189 | "xmlbuilder": {
190 | "version": "11.0.1",
191 | "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
192 | "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="
193 | }
194 | }
195 | }
196 |
--------------------------------------------------------------------------------
/wifi-repeater/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "balenarepeater",
3 | "version": "0.1.0",
4 | "description": "A WiFi/Ethernet network repeater",
5 | "main": "build/index.js",
6 | "scripts": {
7 | "build": "tsc --project tsconfig.json"
8 | },
9 | "author": "Tomás Migone ",
10 | "license": "MIT",
11 | "dependencies": {
12 | "blinking": "0.0.3",
13 | "bluebird": "^3.7.2",
14 | "dbus-native": "^0.4.0",
15 | "typescript": "^3.8.3"
16 | },
17 | "devDependencies": {
18 | "@types/node": "^13.9.0"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/wifi-repeater/src/dbus.ts:
--------------------------------------------------------------------------------
1 | import * as Bluebird from "bluebird";
2 | import { systemBus, Message, Bus } from 'dbus-native';
3 |
4 | export const dbus: Bus = systemBus();
5 |
6 | export const dbusInvoker = (message: Message): PromiseLike => {
7 | return Bluebird.fromCallback(cb => {
8 | return dbus.invoke(message, cb);
9 | });
10 | };
11 |
12 | export const getProperty = async (service: string, objectPath: string, objectInterface: string, property: string): Promise => {
13 | let message: Message = {
14 | destination: service,
15 | path: objectPath,
16 | interface: 'org.freedesktop.DBus.Properties',
17 | member: 'Get',
18 | signature: 'ss',
19 | body: [objectInterface, property]
20 | }
21 | const [[], [value]] = await dbusInvoker(message);
22 | return value;
23 | };
24 |
25 |
--------------------------------------------------------------------------------
/wifi-repeater/src/index.ts:
--------------------------------------------------------------------------------
1 | import * as blinking from 'blinking';
2 | import {
3 | createAccessPoint,
4 | connectToWifi,
5 | checkNMConnectivity,
6 | getWiFiDevices,
7 | getWiredDevices
8 | } from "./nm";
9 |
10 | // defaults
11 | const AP_SSID = process.env.AP_SSID || `P2PHotspot`;
12 | const AP_PASSWORD = process.env.AP_PASSWORD ? (process.env.AP_PASSWORD.length < 8 ? null : process.env.AP_PASSWORD) : null;
13 | const WIFI_SSID = process.env.WIFI_SSID;
14 | const WIFI_PASSWORD = process.env.WIFI_PASSWORD;
15 |
16 | // LED notifications
17 | const ledFile = '/sys/class/leds/led0/brightness';
18 | const led = blinking(ledFile);
19 | const LED_ERROR_PATTERNS = {
20 | NO_AP_CAPABLE_DEVICE: 2,
21 | NO_SECONDARY_WIRELESS: 3,
22 | NO_WIFI_CREDENTIALS: 4,
23 | NO_INTERNET: 5
24 | };
25 |
26 | (async () => {
27 | console.log('-- WiFi repeater: starting...');
28 |
29 | // Get available devices
30 | const wifiDevices = await getWiFiDevices();
31 | const wiredDevices = await getWiredDevices();
32 | console.log(`Wireless interfaces found: ${wifiDevices.map(d => d.iface).join(', ')}`);
33 | console.log(`Wired interfaces found: ${wiredDevices.map(d => d.iface).join(', ')}`);
34 |
35 | // Get available devices and find out which are useful. Only interested in:
36 | // - accessPoint: Any wireless device capable of creating an AP
37 | // - bridge: Any wireless device excluding accessPoint device
38 | // - ethernet: Any wired device that has internet connectivity
39 | const accessPoint = wifiDevices.find(device => device.apCapable);
40 | const bridge = wifiDevices.find(device => device.iface !== accessPoint?.iface);
41 | const ethernet = wiredDevices.find(device => device.connected);
42 |
43 | // Create Access Point, required for both modes of operation
44 | if (!accessPoint) {
45 | console.log(`Could not find a wireless device with AP capabilities. Exiting...`);
46 | led.pattern.start({ blinks: LED_ERROR_PATTERNS.NO_AP_CAPABLE_DEVICE, pause: 1000 });
47 | return;
48 | }
49 |
50 | console.log(`Creating WiFi AP on ${accessPoint.iface} with SSID "${AP_SSID}" and password "${AP_PASSWORD}"...`);
51 | await createAccessPoint({ iface: accessPoint.iface, ssid: AP_SSID, password: AP_PASSWORD });
52 |
53 | // Use secondary wireless device for internet if ethernet doesn't do the job.
54 | if (!ethernet) {
55 | console.log(`Ethernet device has no internet. Attempting to use secondary wireless device to connect to WiFi...`);
56 |
57 | if (!bridge) {
58 | console.log(`Could not find a secondary wireless device. Exiting...`);
59 | led.pattern.start({ blinks: LED_ERROR_PATTERNS.NO_SECONDARY_WIRELESS, pause: 1000 });
60 | return;
61 | }
62 |
63 | if (!WIFI_SSID || !WIFI_PASSWORD) {
64 | console.log(`WiFi credentials for secondary wireless device not provided. Exiting...`);
65 | led.pattern.start({ blinks: LED_ERROR_PATTERNS.NO_WIFI_CREDENTIALS, pause: 1000 });
66 | return;
67 | }
68 |
69 | // Connect secondary wireless interface to WiFi
70 | console.log(`Connecting ${bridge.iface} to WiFi with SSID "${WIFI_SSID}" and password "${WIFI_PASSWORD}"`);
71 | await connectToWifi({ iface: bridge.iface, ssid: WIFI_SSID, password: WIFI_PASSWORD });
72 |
73 | // Check if we are now connected to the internet
74 | let nmConnected = await checkNMConnectivity();
75 | if (!nmConnected) {
76 | console.log(`Warning: Could not detect internet access. Bad WiFi credentials provided or WiFi network has no internet access...`);
77 | led.pattern.start({ blinks: LED_ERROR_PATTERNS.NO_INTERNET, pause: 1000 });
78 | return;
79 | }
80 |
81 | console.log(`WiFi repeater started in REPEATER mode.`);
82 | } else {
83 | console.log(`WiFi repeater started in AP mode.`);
84 | }
85 |
86 | })();
--------------------------------------------------------------------------------
/wifi-repeater/src/nm.ts:
--------------------------------------------------------------------------------
1 | import { NetworkManagerTypes } from './types';
2 | import { BodyEntry } from 'dbus-native';
3 | import {
4 | dbusInvoker,
5 | getProperty
6 | } from './dbus';
7 |
8 | export interface NetworkDevice {
9 | iface: string; // IP interface name
10 | path: string; // DBus object path
11 | type: string;
12 | driver: string;
13 | connected: boolean;
14 | }
15 |
16 | export interface WirelessDevice extends NetworkDevice {
17 | apCapable: boolean;
18 | }
19 |
20 | export interface WiredDevice extends NetworkDevice {
21 | }
22 |
23 | export interface WirelessNetwork {
24 | iface: string;
25 | ssid: string;
26 | password?: string | null | undefined;
27 | }
28 |
29 | const nm: string = 'org.freedesktop.NetworkManager';
30 |
31 | // Wireless
32 | export const createAccessPoint = async (device: WirelessNetwork): Promise => {
33 | try {
34 | // Error out if the interface does not exist
35 | const wifiDevices: NetworkDevice[] = await getWiFiDevices()
36 |
37 | if (!wifiDevices.some(d => d.iface === device.iface)) {
38 | console.log(`Selected interface ${device.iface} does not exist. Hotspot creation aborted...`);
39 | return
40 | }
41 |
42 | let connectionParams: BodyEntry[] = [
43 | [
44 | "connection",
45 | [
46 | ["id", ["s", device.ssid]],
47 | ["type", ["s", "802-11-wireless"]],
48 | ],
49 | ],
50 | [
51 | "802-11-wireless",
52 | [
53 | ["ssid", ["ay", stringToArrayOfBytes(device.ssid)]],
54 | ["mode", ["s", "ap"]],
55 | ],
56 | ],
57 | ["ipv4", [["method", ["s", "shared"]]]],
58 | ["ipv6", [["method", ["s", "ignore"]]]],
59 | ];
60 | if (device.password) {
61 | connectionParams.push([
62 | "802-11-wireless-security",
63 | [
64 | ["key-mgmt", ["s", "wpa-psk"]],
65 | ["psk", ["s", device.password]],
66 | ],
67 | ])
68 | }
69 |
70 | const dbusPath = await getPathByIface(device.iface);
71 | const connection = await addConnection(connectionParams);
72 | const result = await activateConnection(connection, dbusPath);
73 | return result
74 | } catch (error) {
75 | console.log(`Error creating Hotspot: ${error}`);
76 | }
77 | };
78 |
79 | export const connectToWifi = async (network: WirelessNetwork): Promise => {
80 | try {
81 | const connectionParams = [
82 | ['connection', [
83 | ['id', ['s', network.ssid]],
84 | ['type', ['s', '802-11-wireless']],
85 | ]],
86 | ['802-11-wireless', [
87 | ['ssid', ['ay', stringToArrayOfBytes(network.ssid)]],
88 | ['mode', ['s', 'infrastructure']],
89 | ]],
90 | ['802-11-wireless-security', [
91 | ['key-mgmt', ['s', 'wpa-psk']],
92 | ['psk', ['s', network.password]],
93 | ]],
94 | ['ipv4', [
95 | ['method', ['s', 'auto']],
96 | ]],
97 | ['ipv6', [
98 | ['method', ['s', 'auto']],
99 | ]],
100 | ];
101 |
102 | let device = await getPathByIface(network.iface);
103 | let connection = await addConnection(connectionParams);
104 | let result = await activateConnection(connection, device);
105 | return result
106 | } catch (error) {
107 | console.log(`Error connecting to WiFi: ${error}`);
108 | }
109 | };
110 |
111 | // NetworkManager
112 | export const getWiFiDevices = async (): Promise => {
113 | const devices: NetworkDevice[] = await getDevicesByType(NetworkManagerTypes.DEVICE_TYPE.WIFI)
114 | const wifiDevices: WirelessDevice[] = []
115 |
116 | for await (const device of devices) {
117 | const apCapable: boolean = !!(await getProperty(nm, device.path, 'org.freedesktop.NetworkManager.Device.Wireless', 'WirelessCapabilities') & NetworkManagerTypes.WIFI_DEVICE_CAP.AP);
118 | wifiDevices.push({ ...device, apCapable });
119 | }
120 |
121 | return wifiDevices;
122 | };
123 |
124 | export const getWiredDevices = async (): Promise => {
125 | return await getDevicesByType(NetworkManagerTypes.DEVICE_TYPE.ETHERNET);
126 | };
127 |
128 | export const getDevicesByType = async (type: number): Promise => {
129 | const paths: string[] = await getDevicesPath();
130 | const devices: NetworkDevice[] = [];
131 |
132 | for await (const path of paths) {
133 | const deviceType: number = await getProperty(nm, path, 'org.freedesktop.NetworkManager.Device', 'DeviceType');
134 |
135 | if (deviceType === type) {
136 | const iface: string = await getProperty(nm, path, 'org.freedesktop.NetworkManager.Device', 'Interface');
137 | const connected: boolean = await getProperty(nm, path, 'org.freedesktop.NetworkManager.Device', 'Ip4Connectivity') === NetworkManagerTypes.CONNECTIVITY.FULL;
138 | const driver: string = await getProperty(nm, path, 'org.freedesktop.NetworkManager.Device', 'Driver');
139 | const typeName: string = Object.keys(NetworkManagerTypes.DEVICE_TYPE).find(key => NetworkManagerTypes.DEVICE_TYPE[key] === type) || "UNKNOWN";
140 | devices.push({ path, iface, connected, driver, type: typeName });
141 | }
142 | }
143 |
144 | return devices
145 | };
146 |
147 | export const getDevicesPath = async (): Promise => {
148 | return await dbusInvoker({
149 | destination: nm,
150 | path: '/org/freedesktop/NetworkManager',
151 | interface: 'org.freedesktop.NetworkManager',
152 | member: 'GetDevices'
153 | });
154 | };
155 |
156 | export const getPathByIface = async (iface: string): Promise => {
157 | return await dbusInvoker({
158 | destination: nm,
159 | path: '/org/freedesktop/NetworkManager',
160 | interface: 'org.freedesktop.NetworkManager',
161 | member: 'GetDeviceByIpIface',
162 | signature: 's',
163 | body: [iface]
164 | });
165 | };
166 |
167 | export const checkDeviceConnectivity = async (iface: string): Promise => {
168 | const path: string = await getPathByIface(iface)
169 | return await getProperty(nm, path, 'org.freedesktop.NetworkManager.Device', 'Ip4Connectivity')
170 | };
171 |
172 | export const checkNMConnectivity = async (): Promise => {
173 | let nmConnectivityState = await dbusInvoker({
174 | destination: nm,
175 | path: '/org/freedesktop/NetworkManager',
176 | interface: 'org.freedesktop.NetworkManager',
177 | member: 'CheckConnectivity'
178 | });
179 |
180 | return nmConnectivityState === NetworkManagerTypes.CONNECTIVITY.FULL
181 | };
182 |
183 | export const addConnection = async (params: BodyEntry[]): Promise => {
184 | return await dbusInvoker({
185 | destination: nm,
186 | path: '/org/freedesktop/NetworkManager/Settings',
187 | interface: 'org.freedesktop.NetworkManager.Settings',
188 | member: 'AddConnection',
189 | signature: 'a{sa{sv}}',
190 | body: [params]
191 | });
192 | };
193 |
194 | export const activateConnection = async (connection: string, path: string) => {
195 | return await dbusInvoker({
196 | destination: nm,
197 | path: '/org/freedesktop/NetworkManager',
198 | interface: 'org.freedesktop.NetworkManager',
199 | member: 'ActivateConnection',
200 | signature: 'ooo',
201 | body: [connection, path, '/']
202 | });
203 | };
204 |
205 | function stringToArrayOfBytes(str) {
206 | const bytes: number[] = [];
207 | for (let i = 0; i < str.length; ++i) {
208 | bytes.push(str.charCodeAt(i));
209 | }
210 |
211 | return bytes;
212 | }
--------------------------------------------------------------------------------
/wifi-repeater/src/types/index.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 balena.io
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | export class NetworkManagerTypes {
18 | /**
19 | * NMCapability
20 | * NOTE: The range 0x7000 - 0x7FFF of capabilities is guaranteed
21 | * not to be used by upstream NetworkManager.
22 | * It could thus be used for downstream extensions.
23 | * @enum {Number}
24 | */
25 | static CAPABILITY = {
26 | // Teams can be managed
27 | TEAM: 1,
28 | };
29 |
30 | /**
31 | * NMState
32 | * @enum {Number}
33 | */
34 | static STATE = {
35 | /**
36 | * Networking state is unknown;
37 | * Indicates a daemon error
38 | */
39 | UNKNOWN: 0,
40 | /**
41 | * Networking is not enabled;
42 | * System is being suspended or resumed
43 | */
44 | ASLEEP: 10,
45 | /**
46 | * No active network connection
47 | */
48 | DISCONNECTED: 20,
49 | /**
50 | * Network connections are being cleaned up;
51 | * Applications should tear down their network sessions
52 | */
53 | DISCONNECTING: 30,
54 | /**
55 | * Network connection is being started
56 | */
57 | CONNECTING: 40,
58 | /**
59 | * Only local IPv4 and/or IPv6 connectivity,
60 | * but no default route to access the Internet
61 | */
62 | CONNECTED_LOCAL: 50,
63 | /**
64 | * Only site-wide IPv4 and/or IPv6 connectivity;
65 | * Means a default route is available,
66 | * but the Internet connectivity check did not succeed
67 | */
68 | CONNECTED_SITE: 60,
69 | /**
70 | * Global IPv4 and/or IPv6 Internet connectivity
71 | */
72 | CONNECTED_GLOBAL: 70,
73 | };
74 |
75 | /**
76 | * NMConnectivityState
77 | * @enum {Number}
78 | */
79 | static CONNECTIVITY = {
80 | /**
81 | * Network connectivity is unknown;
82 | * Connectivity checks disabled, or not run yet
83 | */
84 | UNKNOWN: 0,
85 | /**
86 | * Host is not connected to any network
87 | */
88 | NONE: 1,
89 | /**
90 | * Internet connection is hijacked by a captive portal gateway
91 | */
92 | PORTAL: 2,
93 | /**
94 | * Host is connected to a network,
95 | * not able to reach the full Internet,
96 | * but a captive portal has not been detected
97 | */
98 | LIMITED: 3,
99 | /**
100 | * Host is connected to a network,
101 | * and able to reach the full Internet
102 | */
103 | FULL: 4,
104 | };
105 |
106 | /**
107 | * NMDeviceType
108 | * @enum {Number}
109 | */
110 | static DEVICE_TYPE = {
111 | /** Unknown device */
112 | UNKNOWN: 0,
113 | /** Generic support for unrecognized device types */
114 | GENERIC: 14,
115 | /** A wired ethernet device */
116 | ETHERNET: 1,
117 | /** An 802.11 WiFi device */
118 | WIFI: 2,
119 | /** Not used */
120 | UNUSED1: 3,
121 | /** Not used */
122 | UNUSED2: 4,
123 | /** A Bluetooth device supporting PAN or DUN access protocols */
124 | BT: 5,
125 | /** An OLPC XO mesh networking device */
126 | OLPC_MESH: 6,
127 | /** An 802.16e Mobile WiMAX broadband device */
128 | WIMAX: 7,
129 | /**
130 | * A modem supporting analog telephone,
131 | * CDMA/EVDO, GSM/UMTS, or LTE network access protocols
132 | */
133 | MODEM: 8,
134 | /** An IP-over-InfiniBand device */
135 | INFINIBAND: 9,
136 | /** A bond master interface */
137 | BOND: 10,
138 | /** An 802.1Q VLAN interface */
139 | VLAN: 11,
140 | /** ADSL modem */
141 | ADSL: 12,
142 | /** A bridge master interface */
143 | BRIDGE: 13,
144 | /** A team master interface */
145 | TEAM: 15,
146 | /** A TUN or TAP interface */
147 | TUN: 16,
148 | /** A IP tunnel interface */
149 | IP_TUNNEL: 17,
150 | /** A MACVLAN interface */
151 | MACVLAN: 18,
152 | /** A VXLAN interface */
153 | VXLAN: 19,
154 | /** A VETH interface */
155 | VETH: 20,
156 | /** A MACsec interface */
157 | MACSEC: 21,
158 | /** A dummy interface */
159 | DUMMY: 22,
160 | /** A PPP interface */
161 | PPP: 23,
162 | /** A OpenVSwitch interface */
163 | OVS_INTERFACE: 24,
164 | /** A OpenVSwitch port */
165 | OVS_PORT: 25,
166 | /** A OpenVSwitch bridge */
167 | OVS_BRIDGE: 26,
168 | };
169 |
170 | /**
171 | * NMDeviceCapabilities
172 | * @enum {Number}
173 | */
174 | static DEVICE_CAP = {
175 | /** Device has no special capabilities */
176 | NONE: 0x00000000,
177 | /** NetworkManager supports this device */
178 | NM_SUPPORTED: 0x00000001,
179 | /** This device can indicate carrier status */
180 | CARRIER_DETECT: 0x00000002,
181 | /** This device is a software device */
182 | IS_SOFTWARE: 0x00000003,
183 | /** This device supports single-root I/O virtualization */
184 | SRIOV: 0x00000004,
185 | };
186 |
187 | /**
188 | * NMDeviceWifiCapabilities
189 | * @enum {Number}
190 | */
191 | static WIFI_DEVICE_CAP = {
192 | /** Device has no encryption/authentication capabilities */
193 | NONE: 0x00000000,
194 | /** Device supports 40/64-bit WEP encryption */
195 | CIPHER_WEP40: 0x00000001,
196 | /** Device supports 104/128-bit WEP encryption */
197 | CIPHER_WEP104: 0x00000002,
198 | /** Device supports TKIP encryption */
199 | CIPHER_TKIP: 0x00000004,
200 | /** Device supports AES/CCMP encryption */
201 | CIPHER_CCMP: 0x00000008,
202 | /** Device supports WPA1 authentication */
203 | WPA: 0x00000010,
204 | /** Device supports WPA2/RSN authentication */
205 | RSN: 0x00000020,
206 | /** Device supports Access Point mode */
207 | AP: 0x00000040,
208 | /** Device supports Ad-Hoc mode */
209 | ADHOC: 0x00000080,
210 | /** Device reports frequency capabilities */
211 | FREQ_VALID: 0x00000100,
212 | /** Device supports 2.4GHz frequencies */
213 | FREQ_2GHZ: 0x00000200,
214 | /** Device supports 5GHz frequencies */
215 | FREQ_5GHZ: 0x00000400,
216 | };
217 |
218 | /**
219 | * NM80211ApFlags
220 | * @enum {Number}
221 | */
222 | static AP_802_11S = {
223 | /** Access point has no special capabilities */
224 | NONE: 0x00000000,
225 | /** Access point requires authentication and encryption (usually means WEP) */
226 | PRIVACY: 0x00000001,
227 | /** Access point supports some WPS method */
228 | WPS: 0x00000002,
229 | /** Access point supports push-button WPS */
230 | WPS_PBC: 0x00000004,
231 | /** Access point supports PIN-based WPS */
232 | WPS_PIN: 0x00000008,
233 | };
234 |
235 | /**
236 | * NM80211ApSecurityFlags
237 | * @enum {Number}
238 | * @description current security requirements of an
239 | * access point as determined from the access point's beacon
240 | */
241 | static AP_802_11_SEC = {
242 | /** The access point has no special security requirements */
243 | NONE: 0x00000000,
244 | /** 40/64-bit WEP is supported for pairwise/unicast encryption */
245 | PAIR_WEP40: 0x00000001,
246 | /** 104/128-bit WEP is supported for pairwise/unicast encryption */
247 | PAIR_WEP104: 0x00000002,
248 | /** TKIP is supported for pairwise/unicast encryption */
249 | PAIR_TKIP: 0x00000004,
250 | /** AES/CCMP is supported for pairwise/unicast encryption */
251 | PAIR_CCMP: 0x00000008,
252 | /** 40/64-bit WEP is supported for group/broadcast encryption */
253 | GROUP_WEP40: 0x00000010,
254 | /** 104/128-bit WEP is supported for group/broadcast encryption */
255 | GROUP_WEP104: 0x00000020,
256 | /** TKIP is supported for group/broadcast encryption */
257 | GROUP_TKIP: 0x00000040,
258 | /** AES/CCMP is supported for group/broadcast encryption */
259 | GROUP_CCMP: 0x00000080,
260 | /** WPA/RSN Pre-Shared Key encryption is supported */
261 | KEY_MGMT_PSK: 0x00000100,
262 | /** 802.1x authentication and key management is supported */
263 | KEY_MGMT_802_1X: 0x00000200,
264 | };
265 |
266 | /**
267 | * NM80211Mode
268 | * @enum {Number}
269 | * @description 802.11 mode an access point or device is currently in
270 | */
271 | static MODE_802_11 = {
272 | /** The device or access point mode is unknown */
273 | UNKNOWN: 0,
274 | /**
275 | * For both devices and access point objects,
276 | * indicates the object is part of an Ad-Hoc 802.11 network
277 | * without a central coordinating access point.
278 | */
279 | ADHOC: 1,
280 | /**
281 | * The device or access point is in infrastructure mode.
282 | * For devices, this indicates the device is an 802.11 client/station.
283 | * For access point objects, this indicates the object is an access point
284 | * that provides connectivity to clients.
285 | */
286 | INFRA: 2,
287 | /**
288 | * The device is an access point/hotspot.
289 | * Not valid for access point objects;
290 | * used only for hotspot mode on the local machine.
291 | */
292 | AP: 3,
293 | };
294 |
295 | /**
296 | * NMBluetoothCapabilities
297 | * @enum {Number}
298 | * @description Usable capabilities of a Bluetooth device
299 | */
300 | static BT_CAPABILITY = {
301 | /** Device has no usable capabilities */
302 | NONE: 0x00000000,
303 | /** Device provides Dial-Up Networking capability */
304 | DUN: 0x00000001,
305 | /** Device provides Network Access Point capability */
306 | NAP: 0x00000002,
307 | };
308 |
309 | /**
310 | * NMDeviceModemCapabilities
311 | * @enum {Number}
312 | * @description Radio access technology families a modem device supports.
313 | * For more information on the specific access technologies
314 | * the device supports use the ModemManager D-Bus API
315 | */
316 | static DEVICE_MODEM_CAPABILITY = {
317 | /** Modem has no usable capabilities */
318 | NONE: 0x00000000,
319 | /** Modem uses the analog wired telephone network and is not a wireless/cellular device */
320 | POTS: 0x00000001,
321 | /** Modem supports at least one of CDMA 1xRTT, EVDO revision 0, EVDO revision A, or EVDO revision B */
322 | CDMA_EVDO: 0x00000002,
323 | /** Modem supports at least one of GSM, GPRS, EDGE, UMTS, HSDPA, HSUPA, or HSPA+ packet switched data capability */
324 | GSM_UMTS: 0x00000004,
325 | /** Modem has LTE data capability */
326 | LTE: 0x00000008,
327 | };
328 |
329 | /**
330 | * NMWimaxNspNetworkType
331 | * @enum {Number}
332 | */
333 | static WIMAX_NSP_NETWORK_TYPE = {
334 | /** Unknown network type */
335 | UNKNOWN: 0,
336 | /** Home network */
337 | HOME: 1,
338 | /** Partner network */
339 | PARTNER: 2,
340 | /** Roaming partner network */
341 | ROAMING_PARTNER: 3,
342 | };
343 |
344 | /**
345 | * NMDeviceState
346 | * @enum {Number}
347 | */
348 | static DEVICE_STATE = {
349 | /**
350 | * The device's state is unknown
351 | * @type {Number}
352 | */
353 | UNKNOWN: 0,
354 | /**
355 | * The device is recognized, but not managed by NetworkManager
356 | * @type {Number}
357 | */
358 | UNMANAGED: 10,
359 | /**
360 | * The device is managed by NetworkManager, but is not available for use.
361 | * Reasons may include the wireless switched off, missing firmware,
362 | * no ethernet carrier, missing supplicant or modem manager, etc.
363 | * @type {Number}
364 | */
365 | UNAVAILABLE: 20,
366 | /**
367 | * The device can be activated, but is currently idle and not connected to a network.
368 | * @type {Number}
369 | */
370 | DISCONNECTED: 30,
371 | /**
372 | * The device is preparing the connection to the network.
373 | * This may include operations like changing the MAC address,
374 | * setting physical link properties,
375 | * and anything else required to connect to the requested network.
376 | * @type {Number}
377 | */
378 | PREPARE: 40,
379 | /**
380 | * The device is connecting to the requested network.
381 | * This may include operations like associating with the WiFi AP,
382 | * dialing the modem, connecting to the remote Bluetooth device, etc.
383 | * @type {Number}
384 | */
385 | CONFIG: 50,
386 | /**
387 | * The device requires more information to continue connecting to the requested network.
388 | * This includes secrets like WiFi passphrases, login passwords, PIN codes, etc.
389 | * @type {Number}
390 | */
391 | NEED_AUTH: 60,
392 | /**
393 | * The device is requesting IPv4 and/or IPv6 addresses
394 | * and routing information from the network.
395 | * @type {Number}
396 | */
397 | IP_CONFIG: 70,
398 | /**
399 | * The device is checking whether further action is required for the requested network connection.
400 | * This may include checking whether only local network access is available,
401 | * whether a captive portal is blocking access to the Internet, etc.
402 | * @type {Number}
403 | */
404 | IP_CHECK: 80,
405 | /**
406 | * The device is waiting for a secondary connection (like a VPN)
407 | * which must activated before the device can be activated
408 | * @type {Number}
409 | */
410 | SECONDARIES: 90,
411 | /**
412 | * The device has a network connection, either local or global.
413 | * @type {Number}
414 | */
415 | ACTIVATED: 100,
416 | /**
417 | * A disconnection from the current network connection was requested,
418 | * and the device is cleaning up resources used for that connection.
419 | * The network connection may still be valid.
420 | * @type {Number}
421 | */
422 | DEACTIVATING: 110,
423 | /**
424 | * The device failed to connect to the requested network and
425 | * is cleaning up the connection request
426 | * @type {Number}
427 | */
428 | FAILED: 120,
429 | };
430 |
431 | /**
432 | * NMDeviceStateReason
433 | * @enum {Number}
434 | */
435 | static DEVICE_STATE_REASON = {
436 | /** No reason given */
437 | NONE: 0,
438 | /** Unknown error */
439 | UNKNOWN: 1,
440 | /** Device is now managed */
441 | NOW_MANAGED: 2,
442 | /** Device is now unmanaged */
443 | NOW_UNMANAGED: 3,
444 | /** The device could not be readied for configuration */
445 | CONFIG_FAILED: 4,
446 | /** IP configuration could not be reserved (no available address, timeout, etc) */
447 | IP_CONFIG_UNAVAILABLE: 5,
448 | /** The IP config is no longer valid */
449 | IP_CONFIG_EXPIRED: 6,
450 | /** Secrets were required, but not provided */
451 | NO_SECRETS: 7,
452 | /** 802.1x supplicant disconnected */
453 | SUPPLICANT_DISCONNECT: 8,
454 | /** 802.1x supplicant configuration failed */
455 | SUPPLICANT_CONFIG_FAILED: 9,
456 | /** 802.1x supplicant failed */
457 | SUPPLICANT_FAILED: 10,
458 | /** 802.1x supplicant took too long to authenticate */
459 | SUPPLICANT_TIMEOUT: 11,
460 | /** PPP service failed to start */
461 | PPP_START_FAILED: 12,
462 | /** PPP service disconnected */
463 | PPP_DISCONNECT: 13,
464 | /** PPP failed */
465 | PPP_FAILED: 14,
466 | /** DHCP client failed to start */
467 | DHCP_START_FAILED: 15,
468 | /** DHCP client error */
469 | DHCP_ERROR: 16,
470 | /** DHCP client failed */
471 | DHCP_FAILED: 17,
472 | /** Shared connection service failed to start */
473 | SHARED_START_FAILED: 18,
474 | /** Shared connection service failed */
475 | SHARED_FAILED: 19,
476 | /** AutoIP service failed to start */
477 | AUTOIP_START_FAILED: 20,
478 | /** AutoIP service error */
479 | AUTOIP_ERROR: 21,
480 | /** AutoIP service failed */
481 | AUTOIP_FAILED: 22,
482 | /** The line is busy */
483 | MODEM_BUSY: 23,
484 | /** No dial tone */
485 | MODEM_NO_DIAL_TONE: 24,
486 | /** No carrier could be established */
487 | MODEM_NO_CARRIER: 25,
488 | /** The dialing request timed out */
489 | MODEM_DIAL_TIMEOUT: 26,
490 | /** The dialing attempt failed */
491 | MODEM_DIAL_FAILED: 27,
492 | /** Modem initialization failed */
493 | MODEM_INIT_FAILED: 28,
494 | /** Failed to select the specified APN */
495 | GSM_APN_FAILED: 29,
496 | /** Not searching for networks */
497 | GSM_REGISTRATION_NOT_SEARCHING: 30,
498 | /** Network registration denied */
499 | GSM_REGISTRATION_DENIED: 31,
500 | /** Network registration timed out */
501 | GSM_REGISTRATION_TIMEOUT: 32,
502 | /** Failed to register with the requested network */
503 | GSM_REGISTRATION_FAILED: 33,
504 | /** PIN check failed */
505 | GSM_PIN_CHECK_FAILED: 34,
506 | /** Necessary firmware for the device may be missing */
507 | FIRMWARE_MISSING: 35,
508 | /** The device was removed */
509 | REMOVED: 36,
510 | /** NetworkManager went to sleep */
511 | SLEEPING: 37,
512 | /** The device's active connection disappeared */
513 | CONNECTION_REMOVED: 38,
514 | /** Device disconnected by user or client */
515 | USER_REQUESTED: 39,
516 | /** Carrier/link changed */
517 | CARRIER: 40,
518 | /** The device's existing connection was assumed */
519 | CONNECTION_ASSUMED: 41,
520 | /** The supplicant is now available */
521 | SUPPLICANT_AVAILABLE: 42,
522 | /** The modem could not be found */
523 | MODEM_NOT_FOUND: 43,
524 | /** The Bluetooth connection failed or timed out */
525 | BT_FAILED: 44,
526 | /** GSM Modem's SIM Card not inserted */
527 | GSM_SIM_NOT_INSERTED: 45,
528 | /** GSM Modem's SIM Pin required */
529 | GSM_SIM_PIN_REQUIRED: 46,
530 | /** GSM Modem's SIM Puk required */
531 | GSM_SIM_PUK_REQUIRED: 47,
532 | /** GSM Modem's SIM wrong */
533 | GSM_SIM_WRONG: 48,
534 | /** InfiniBand device does not support connected mode */
535 | INFINIBAND_MODE: 49,
536 | /** A dependency of the connection failed */
537 | DEPENDENCY_FAILED: 50,
538 | /** Problem with the RFC 2684 Ethernet over ADSL bridge */
539 | BR2684_FAILED: 51,
540 | /** ModemManager not running */
541 | MODEM_MANAGER_UNAVAILABLE: 52,
542 | /** The WiFi network could not be found */
543 | SSID_NOT_FOUND: 53,
544 | /** A secondary connection of the base connection failed */
545 | SECONDARY_CONNECTION_FAILED: 54,
546 | /** DCB or FCoE setup failed */
547 | DCB_FCOE_FAILED: 55,
548 | /** Teamd control failed */
549 | TEAMD_CONTROL_FAILED: 56,
550 | /** Modem failed or no longer available */
551 | MODEM_FAILED: 57,
552 | /** Modem now ready and available */
553 | MODEM_AVAILABLE: 58,
554 | /** SIM PIN was incorrect */
555 | SIM_PIN_INCORRECT: 59,
556 | /** New connection activation was enqueued */
557 | NEW_ACTIVATION: 60,
558 | /** The device's parent changed */
559 | PARENT_CHANGED: 61,
560 | /** The device parent's management changed */
561 | PARENT_MANAGED_CHANGED: 62,
562 | /** Problem communicating with OpenVSwitch database */
563 | OVSDB_FAILED: 63,
564 | /** A duplicate IP address was detected */
565 | IP_ADDRESS_DUPLICATE: 64,
566 | /** The selected IP method is not supported */
567 | IP_METHOD_UNSUPPORTED: 65,
568 | };
569 |
570 | /**
571 | * NMMetered
572 | * Since: v1.2
573 | * @enum {Number}
574 | */
575 | static METERED = {
576 | /** The metered status is unknown */
577 | UNKNOWN: 0,
578 | /** Metered, the value was statically set */
579 | YES: 1,
580 | /** Not metered, the value was statically set */
581 | NO: 2,
582 | /** Metered, the value was guessed */
583 | GUESS_YES: 3,
584 | /** Not metered, the value was guessed */
585 | GUESS_NO: 4,
586 | };
587 |
588 | /**
589 | * NMActiveConnectionState
590 | * NOTE: NMActiveConnectionState values indicate the state of a
591 | * connection to a specific network while it is starting,
592 | * connected, or disconnecting from that network.
593 | * @enum {Number}
594 | */
595 | static ACTIVE_CONNECTION_STATE = {
596 | /** The state of the connection is unknown */
597 | UNKNOWN: 0,
598 | /** A network connection is being prepared */
599 | ACTIVATING: 1,
600 | /** There is a connection to the network */
601 | ACTIVATED: 2,
602 | /** The network connection is being torn down and cleaned up */
603 | DEACTIVATING: 3,
604 | /** The network connection is disconnected and will be removed */
605 | DEACTIVATED: 4,
606 | };
607 |
608 | /**
609 | * NMActiveConnectionStateReason
610 | * Since: v1.8
611 | * @enum {Number}
612 | */
613 | static ACTIVE_CONNECTION_STATE_REASON = {
614 | /**
615 | * The reason for the active connection state change is unknown.
616 | * @type {Number}
617 | */
618 | UNKNOWN: 0,
619 | /**
620 | * No reason was given for the active connection state change.
621 | * @type {Number}
622 | */
623 | NONE: 1,
624 | /**
625 | * The active connection changed state because the user disconnected it.
626 | * @type {Number}
627 | */
628 | USER_DISCONNECTED: 2,
629 | /**
630 | * The active connection changed state because the device it was using was disconnected.
631 | * @type {Number}
632 | */
633 | DEVICE_DISCONNECTED: 3,
634 | /**
635 | * The service providing the VPN connection was stopped.
636 | * @type {Number}
637 | */
638 | SERVICE_STOPPED: 4,
639 | /**
640 | * The IP config of the active connection was invalid.
641 | * @type {Number}
642 | */
643 | IP_CONFIG_INVALID: 5,
644 | /**
645 | * The connection attempt to the VPN service timed out.
646 | * @type {Number}
647 | */
648 | CONNECT_TIMEOUT: 6,
649 | /**
650 | * A timeout occurred while starting the service providing the VPN connection.
651 | * @type {Number}
652 | */
653 | SERVICE_START_TIMEOUT: 7,
654 | /**
655 | * Starting the service providing the VPN connection failed.
656 | * @type {Number}
657 | */
658 | SERVICE_START_FAILED: 8,
659 | /**
660 | * Necessary secrets for the connection were not provided.
661 | * @type {Number}
662 | */
663 | NO_SECRETS: 9,
664 | /**
665 | * Authentication to the server failed.
666 | * @type {Number}
667 | */
668 | LOGIN_FAILED: 10,
669 | /**
670 | * The connection was deleted from settings.
671 | * @type {Number}
672 | */
673 | CONNECTION_REMOVED: 11,
674 | /**
675 | * Master connection of this connection failed to activate.
676 | * @type {Number}
677 | */
678 | DEPENDENCY_FAILED: 12,
679 | /**
680 | * Could not create the software device link.
681 | * @type {Number}
682 | */
683 | DEVICE_REALIZE_FAILED: 13,
684 | /**
685 | * The device this connection depended on disappeared.
686 | * @type {Number}
687 | */
688 | DEVICE_REMOVED: 14,
689 | };
690 |
691 | /**
692 | * NMSecretAgentGetSecretsFlags
693 | * NOTE: NMSecretAgentGetSecretsFlags values modify the behavior of a GetSecrets request
694 | * @enum {Number}
695 | */
696 | static SECRET_AGENT_GET_SECRETS = {
697 | /**
698 | * No special behavior; by default no user interaction is
699 | * allowed and requests for secrets are fulfilled from persistent storage,
700 | * or if no secrets are available an error is returned.
701 | * @type {Number}
702 | */
703 | NONE: 0x0,
704 | /**
705 | * Allows the request to interact with the user,
706 | * possibly prompting via UI for secrets if any are required,
707 | * or if none are found in persistent storage.
708 | * @type {Number}
709 | */
710 | ALLOW_INTERACTION: 0x1,
711 | /**
712 | * Explicitly prompt for new secrets from the user.
713 | * This flag signals that NetworkManager thinks any existing secrets are invalid or wrong.
714 | * This flag implies that interaction is allowed.
715 | * @type {Number}
716 | */
717 | REQUEST_NEW: 0x2,
718 | /**
719 | * Set if the request was initiated by user-requested action via the D-Bus interface,
720 | * as opposed to automatically initiated by NetworkManager in response to scan results or carrier changes.
721 | * @type {Number}
722 | */
723 | USER_REQUESTED: 0x4,
724 | /**
725 | * Indicates that WPS enrollment is active with PBC method.
726 | * The agent may suggest that the user pushes a button on the router instead of supplying a PSK.
727 | * @type {Number}
728 | */
729 | WPS_PBC_ACTIVE: 0x8,
730 | /**
731 | * Internal flag, not part of the D-Bus API.
732 | * @type {Number}
733 | */
734 | ONLY_SYSTEM: 0x80000000,
735 | /**
736 | * Internal flag, not part of the D-Bus API.
737 | * @type {Number}
738 | */
739 | NO_ERRORS: 0x40000000,
740 | };
741 |
742 | /**
743 | * NMSecretAgentCapabilities
744 | * NOTE: NMSecretAgentCapabilities indicate various capabilities of the agent
745 | * @enum {Number}
746 | */
747 | static SECRET_AGENT_CAPABILITY = {
748 | /**
749 | * The agent supports no special capabilities
750 | * @type {Number}
751 | */
752 | NONE: 0x0,
753 | /**
754 | * The agent supports passing hints to VPN plugin authentication dialogs.
755 | * @type {Number}
756 | */
757 | VPN_HINTS: 0x1,
758 | };
759 |
760 | /**
761 | * NMIPTunnelMode
762 | * Since: v1.2
763 | * @enum {Number}
764 | */
765 | static IP_TUNNEL_MODE = {
766 | /** Unknown/unset tunnel mode */
767 | UNKNOWN: 0,
768 | /** IP in IP tunnel */
769 | IPIP: 1,
770 | /** GRE tunnel */
771 | GRE: 2,
772 | /** SIT tunnel */
773 | SIT: 3,
774 | /** ISATAP tunnel */
775 | ISATAP: 4,
776 | /** VTI tunnel */
777 | VTI: 5,
778 | /** IPv6 in IPv6 tunnel */
779 | IP6IP6: 6,
780 | /** IPv4 in IPv6 tunnel */
781 | IPIP6: 7,
782 | /** IPv6 GRE tunnel */
783 | IP6GRE: 8,
784 | /** IPv6 VTI tunnel */
785 | VTI6: 9,
786 | };
787 |
788 | /**
789 | * NMCheckpointCreateFlags
790 | * Since: v1.4
791 | * @enum {Number}
792 | */
793 | static CHECKPOINT_CREATE = {
794 | /**
795 | * No flags
796 | * @type {Number}
797 | */
798 | NONE: 0,
799 | /**
800 | * When creating a new checkpoint,
801 | * destroy all existing ones.
802 | * @type {Number}
803 | */
804 | DESTROY_ALL: 0x01,
805 | /**
806 | * Upon rollback, delete any new connection added after the checkpoint
807 | * Since: v1.6
808 | * @type {Number}
809 | */
810 | DELETE_NEW_CONNECTIONS: 0x02,
811 | /**
812 | * Upon rollback, disconnect any new device appeared after the checkpoint
813 | * Since: v1.6
814 | * @type {Number}
815 | */
816 | DISCONNECT_NEW_DEVICES: 0x04,
817 | };
818 |
819 | /**
820 | * NMRollbackResult
821 | * Since: v1.4
822 | * @enum {Number}
823 | */
824 | static ROLLBACK_RESULT = {
825 | /** The rollback succeeded. */
826 | OK: 0,
827 | /** The device no longer exists. */
828 | ERR_NO_DEVICE: 1,
829 | /** The device is now unmanaged. */
830 | ERR_DEVICE_UNMANAGED: 2,
831 | /** Other errors during rollback. */
832 | ERR_FAILED: 3,
833 | };
834 |
835 | /**
836 | * NMActivationStateFlags
837 | * Since: v1.10
838 | * @enum {Number}
839 | */
840 | static ACTIVATION_STATE = {
841 | /**
842 | * An alias for numeric zero, no flags set.
843 | * @type {Number}
844 | */
845 | NONE: 0,
846 | /**
847 | * The device is a master.
848 | * @type {Number}
849 | */
850 | IS_MASTER: null, // (1LL
851 | /**
852 | * The device is a slave.
853 | * @type {Number}
854 | */
855 | IS_SLAVE: null, // (1LL
856 | /**
857 | * Layer2 is activated and ready.
858 | * @type {Number}
859 | */
860 | LAYER2_READY: null, // (1LL
861 | /**
862 | * IPv4 setting is completed.
863 | * @type {Number}
864 | */
865 | IP4_READY: null, // (1LL
866 | /**
867 | * IPv6 setting is completed.
868 | * @type {Number}
869 | */
870 | IP6_READY: null, // (1LL
871 | /**
872 | * The master has any slave devices attached.
873 | * This only makes sense if the device is a master.
874 | * @type {Number}
875 | */
876 | MASTER_HAS_SLAVES: null, // (1LL
877 | };
878 |
879 | /**
880 | * NMSettingsUpdate2Flags
881 | * Since: v1.10.2
882 | * @enum {Number}
883 | */
884 | static SETTINGS_UPDATE2 = {
885 | /**
886 | * An alias for numeric zero, no flags set.
887 | * @type {Number}
888 | */
889 | NONE: 0,
890 | /**
891 | * To persist the connection to disk.
892 | * @type {Number}
893 | */
894 | TO_DISK: null, // (1LL
895 | /**
896 | * To make the connection in-memory only.
897 | * If the connection was previously persistent,
898 | * the corresponding file on disk is not deleted but merely the
899 | * connection is decoupled from the file on disk.
900 | * If you later delete an in-memory connection,
901 | * the connection on disk will be deleted as well.
902 | * @type {Number}
903 | */
904 | IN_MEMORY: null, // (1LL
905 | /**
906 | * This is like @NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY,
907 | * but if the connection has a corresponding file on disk,
908 | * the association between the connection and the file is
909 | * forgotten but the file is not modified.
910 | * The difference to %NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY is if you later
911 | * save the connection again to disk, a new file name will be chosen without
912 | * overwriting the remaining file on disk.
913 | * Also, if you delete the connection later,
914 | * the file on disk will not be deleted.
915 | * @type {Number}
916 | */
917 | IN_MEMORY_DETACHED: null, // (1LL
918 | /**
919 | * This is like @NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY,
920 | * but if the connection has a corresponding file on disk,
921 | * the file on disk will be deleted.
922 | * @type {Number}
923 | */
924 | IN_MEMORY_ONLY: null, // (1LL
925 | /**
926 | * This can be specified with either %NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_DETACHED or
927 | * %NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY_ONLY. After making the connection in-memory only,
928 | * the connection is marked as volatile.
929 | * That means, if the connection is currently not active it will be deleted right away.
930 | * Otherwise, it is marked to for deletion once the connection deactivates.
931 | * A volatile connection cannot autoactivate again (because it's about to be deleted),
932 | * but a manual activation will clear the volatile flag.
933 | * @type {Number}
934 | */
935 | VOLATILE: null, // (1LL
936 | /**
937 | * Usually, when the connection has autoconnect enabled and is modified,
938 | * it becomes elegible to autoconnect right away.
939 | * Setting this flag, disables autoconnect until the connection is manually activated.
940 | * @type {Number}
941 | */
942 | BLOCK_AUTOCONNECT: null, // (1LL
943 | };
944 | }
--------------------------------------------------------------------------------
/wifi-repeater/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": false,
4 | "forceConsistentCasingInFileNames": true,
5 | "module": "commonjs",
6 | "noErrorTruncation": true,
7 | "noFallthroughCasesInSwitch": true,
8 | "noImplicitAny": false,
9 | "noUnusedParameters": true,
10 | "noUnusedLocals": true,
11 | "outDir": "build/",
12 | "preserveConstEnums": true,
13 | "removeComments": true,
14 | "resolveJsonModule": true,
15 | "sourceMap": true,
16 | "skipLibCheck": true,
17 | "strictNullChecks": true,
18 | "target": "es2018"
19 | },
20 | "include": [
21 | "./src/**.ts",
22 | "./**.ts",
23 | "./typings/**.d.ts"
24 | ],
25 | "exclude": [
26 | "node_modules"
27 | ]
28 | }
29 |
--------------------------------------------------------------------------------
/wifi-repeater/typings/dbus-native.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'dbus-native' {
2 |
3 | export type BodyEntry = string | number | array | null;
4 |
5 | export interface Message {
6 | destination: string;
7 | path: string;
8 | interface: string;
9 | member: string;
10 | signature?: string;
11 | body?: BodyEntry[];
12 | }
13 |
14 | export interface Bus {
15 | invoke: (
16 | message: Message,
17 | callback: (error: Error, response: any) => void,
18 | ) => void;
19 | }
20 |
21 | export function systemBus(): Bus;
22 |
23 | }
--------------------------------------------------------------------------------