Merge branch 'unstable'
This commit is contained in:
commit
f796c521ef
|
@ -54,6 +54,24 @@ jobs:
|
|||
curl -s -L "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-Country&license_key=$MAX_TOKEN&suffix=tar.gz" -o GeoLite2-Country.tar.gz
|
||||
tar -xzf GeoLite2-Country.tar.gz --strip-components 1 --wildcards "*.mmdb"
|
||||
|
||||
- run:
|
||||
name: Download and Extract ARM Nebula Binary
|
||||
command: |
|
||||
curl -LO https://github.com/slackhq/nebula/releases/download/v1.7.2/nebula-linux-arm64.tar.gz
|
||||
tar -xzvf nebula-linux-arm64.tar.gz
|
||||
|
||||
- run:
|
||||
name: Rename ARM Nebula Binary
|
||||
command: |
|
||||
mv nebula nebula-arm
|
||||
mv nebula-cert nebula-cert-arm
|
||||
|
||||
- run:
|
||||
name: Download and Extract Nebula Binary
|
||||
command: |
|
||||
curl -LO https://github.com/slackhq/nebula/releases/download/v1.7.2/nebula-linux-amd64.tar.gz
|
||||
tar -xzvf nebula-linux-amd64.tar.gz
|
||||
|
||||
- run:
|
||||
name: Build UI
|
||||
command: npm run client-build
|
||||
|
|
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -13,3 +13,9 @@ LICENCE
|
|||
tokens.json
|
||||
.vscode
|
||||
GeoLite2-Country.mmdb
|
||||
dns-blacklist.txt
|
||||
zz_test_config
|
||||
nebula-arm
|
||||
nebula-arm-cert
|
||||
nebula
|
||||
nebula-cert
|
40
LICENCE
40
LICENCE
|
@ -1,3 +1,11 @@
|
|||
Software: Cosmos-Server
|
||||
|
||||
License: Apache 2.0 with Commons Clause and Anti Tampering Clause
|
||||
|
||||
Licensor: Yann Stepienik
|
||||
|
||||
---------------------------------------------------------------------
|
||||
|
||||
“Commons Clause” License Condition v1.0
|
||||
|
||||
The Software is provided to you by the Licensor under the
|
||||
|
@ -17,15 +25,33 @@ of the Software. Any license notice or attribution required by
|
|||
the License must also include this Commons Clause License
|
||||
Condition notice.
|
||||
|
||||
Software: Cosmos-Server
|
||||
|
||||
License: Apache 2.0 with Commons Clause
|
||||
|
||||
Licensor: Yann Stepienik
|
||||
|
||||
|
||||
---------------------------------------------------------------------
|
||||
|
||||
"Anti Tampering Clause” License Condition v1.0
|
||||
|
||||
Notwithstanding any provision of the Apache License 2.0, if the User
|
||||
(or any party receiving or distributing derivative works, services,
|
||||
or anything of value from the User related to the Software), directly
|
||||
or indirectly, seeks to tamper with, alter, circumvent, or avoid
|
||||
compliance with any subscription, paywall, feature restriction, or any
|
||||
other licensing mechanism built into the Software or its usage, the
|
||||
License granted under the Apache License 2.0 shall automatically and
|
||||
immediately terminate, and access to the Software shall be withdrawn
|
||||
with immediate effect. Upon such termination, any and all rights
|
||||
established under the Apache License 2.0 shall be null and void.
|
||||
|
||||
Tampering includes but is not limited to: (a) removing, disabling,
|
||||
or circumventing any license key or other copy protection mechanism,
|
||||
(b) redistributing parts or all of a feature that was intended
|
||||
to be a paid feature, without keeping the restrictions, limitations,
|
||||
or other licensing mechanisms with it(c) disabling, circumventing, or
|
||||
avoiding any feature of the Software that is intended to enforce usage or
|
||||
copy restrictions, or (d) providing or distributing any information
|
||||
or code that enables disabling, circumvention, or avoidance of any
|
||||
feature of the Software that is intended to enforce usage or copy
|
||||
restrictions.
|
||||
|
||||
---------------------------------------------------------------------
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
|
|
1
build.sh
1
build.sh
|
@ -18,6 +18,7 @@ echo " ---- Build complete, copy assets ----"
|
|||
|
||||
cp -r static build/
|
||||
cp -r GeoLite2-Country.mmdb build/
|
||||
cp nebula-arm-cert nebula-cert nebula-arm nebula build/
|
||||
cp -r Logo.png build/
|
||||
mkdir build/images
|
||||
cp client/src/assets/images/icons/cosmos_gray.png build/cosmos_gray.png
|
||||
|
|
16
changelog.md
16
changelog.md
|
@ -1,3 +1,19 @@
|
|||
<<<<<<< HEAD
|
||||
## Version 0.9.20 - 0.9.21
|
||||
- Add option to disable CORS hardening (with empty value)
|
||||
|
||||
=======
|
||||
## Version 0.10.0
|
||||
- Added Constellation
|
||||
- DNS Challenge is now used for all certificates when enabled [breaking change]
|
||||
- Rework headers for better compatibility
|
||||
- Improve experience for non-admin users
|
||||
- Fix bug with redirect on logout
|
||||
- Added OverwriteHostHeader to routes to override the host header sent to the target app
|
||||
- Added WhitelistInboundIPs to routes to filter incoming requests based on IP per URL
|
||||
|
||||
> **Note: If you use the ARM (:latest-arm) you need to manually update to using the :latest tag instead**
|
||||
|
||||
## Version 0.9.20 - 0.9.21
|
||||
- Add option to disable CORS hardening (with empty value)
|
||||
|
||||
|
|
2
cla.md
2
cla.md
|
@ -2,7 +2,7 @@ Cosmos Software Grant and Contributor License Agreement (“Agreement”)
|
|||
|
||||
This agreement is based on the Apache Software Foundation Contributor License Agreement. (v r190612)
|
||||
|
||||
Thank you for your interest in software projects stewarded by Raintank, Inc. dba Cosmos (“Cosmos”). In order to clarify the intellectual property license granted with Contributions from any person or entity, Cosmos must have a Contributor License Agreement (CLA) on file that has been agreed to by each Contributor, indicating agreement to the license terms below. This license is for your protection as a Contributor as well as the protection of Cosmos and its users; it does not change your rights to use your own Contributions for any other purpose. This Agreement allows an individual to contribute to Cosmos on that individual’s own behalf, or an entity (the “Corporation”) to submit Contributions to Cosmos, to authorize Contributions submitted by its designated employees to Cosmos, and to grant copyright and patent licenses thereto.
|
||||
Thank you for your interest in dba Cosmos (“Cosmos”). In order to clarify the intellectual property license granted with Contributions from any person or entity, Cosmos must have a Contributor License Agreement (CLA) on file that has been agreed to by each Contributor, indicating agreement to the license terms below. This license is for your protection as a Contributor as well as the protection of Cosmos and its users; it does not change your rights to use your own Contributions for any other purpose. This Agreement allows an individual to contribute to Cosmos on that individual’s own behalf, or an entity (the “Corporation”) to submit Contributions to Cosmos, to authorize Contributions submitted by its designated employees to Cosmos, and to grant copyright and patent licenses thereto.
|
||||
|
||||
You accept and agree to the following terms and conditions for Your present and future Contributions submitted to Cosmos. Except for the license granted herein to Cosmos and recipients of software distributed by Cosmos, You reserve all right, title, and interest in and to Your Contributions.
|
||||
|
||||
|
|
131
client/src/api/constellation.demo.tsx
Normal file
131
client/src/api/constellation.demo.tsx
Normal file
|
@ -0,0 +1,131 @@
|
|||
import wrap from './wrap';
|
||||
|
||||
function list() {
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve({
|
||||
"data": [
|
||||
{
|
||||
"nickname": "admin",
|
||||
"deviceName": "phone",
|
||||
"publicKey": "-----BEGIN NEBULA X25519 PRIVATE KEY-----\naACf/...=\n-----END NEBULA X25519 PRIVATE KEY-----\n",
|
||||
"ip": "192.168.201.4/24",
|
||||
"isLighthouse": false,
|
||||
"isRelay": true,
|
||||
"publicHostname": "",
|
||||
"port": "4242",
|
||||
"blocked": false,
|
||||
"fingerprint": "..."
|
||||
},
|
||||
{
|
||||
"nickname": "admin",
|
||||
"deviceName": "laptop",
|
||||
"publicKey": "-----BEGIN NEBULA X25519 PRIVATE KEY-----\n78l4nDEB0+.../36YBQk7dkwg+.=\n-----END NEBULA X25519 PRIVATE KEY-----\n",
|
||||
"ip": "192.168.201.5/24",
|
||||
"isLighthouse": false,
|
||||
"isRelay": true,
|
||||
"publicHostname": "",
|
||||
"port": "4242",
|
||||
"blocked": false,
|
||||
"fingerprint": "..."
|
||||
},
|
||||
{
|
||||
"nickname": "Martha",
|
||||
"deviceName": "pink phone",
|
||||
"publicKey": "-----BEGIN NEBULA X25519 PRIVATE KEY-----\naACf/..=\n-----END NEBULA X25519 PRIVATE KEY-----\n",
|
||||
"ip": "192.168.201.6/24",
|
||||
"isLighthouse": false,
|
||||
"isRelay": true,
|
||||
"publicHostname": "",
|
||||
"port": "4242",
|
||||
"blocked": false,
|
||||
"fingerprint": "..."
|
||||
}
|
||||
],
|
||||
"status": "OK"
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function addDevice(device) {
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve({
|
||||
"data": {
|
||||
"CA": "-----BEGIN NEBULA CERTIFICATE-----\....\n+dfE+ikL8jUh/n+C+....\....\nZon/Dw==\n-----END NEBULA CERTIFICATE-----\n",
|
||||
"Config": "constellation_api_key: ...\nconstellation_device_name: test\nconstellation_local_dns_overwrite: true\nconstellation_local_dns_overwrite_address: 192.168.201.1\nconstellation_public_hostname: \"\"\nfirewall:\n conntrack:\n default_timeout: 10m\n tcp_timeout: 12m\n udp_timeout: 3m\n inbound:\n - host: any\n port: any\n proto: any\n inbound_action: drop\n outbound:\n - host: any\n port: any\n proto: any\n outbound_action: drop\nlighthouse:\n am_lighthouse: false\n hosts:\n - 192.168.201.1\n interval: 60\nlisten:\n host: 0.0.0.0\n port: \"4242\"\nlogging:\n format: text\n level: info\npki:\n blocklist: []\n ca: |\n -----BEGIN NEBULA CERTIFICATE-----\n ...\n +dfE+ikL8jUh/n+C+...\n .\n Zon/Dw==\n -----END NEBULA CERTIFICATE-----\n cert: |\n -----BEGIN NEBULA CERTIFICATE-----\n CmIKBHRlc3QSCoeSo4UMgP7//..\n ...+QwZSiBxLdKhjkCH+.../..\n ./hfL+....\n ..==\n -----END NEBULA CERTIFICATE-----\n key: |\n -----BEGIN NEBULA X25519 PRIVATE KEY-----\n nS39dWX7uo1rhTvP2yl2XonGx3fWEkpk+43thNrMu7U=\n -----END NEBULA X25519 PRIVATE KEY-----\npunchy:\n punch: true\n respond: true\nrelay:\n am_relay: false\n relays:\n - 192.168.201.1\n use_relays: true\nstatic_host_map:\n 192.168.201.1:\n - vpn.domain.com:4242\ntun:\n dev: nebula1\n disabled: false\n drop_local_broadcast: false\n drop_multicast: false\n mtu: 1300\n routes: []\n tx_queue: 500\n unsafe_routes: []\n",
|
||||
"DeviceName": "test",
|
||||
"IP": "192.168.201.7/24",
|
||||
"IsLighthouse": false,
|
||||
"IsRelay": true,
|
||||
"LighthousesList": [],
|
||||
"Nickname": "admin",
|
||||
"Port": "4242",
|
||||
"PrivateKey": "-----BEGIN NEBULA CERTIFICATE-----\...//w8o3ZaFqQYwhdGFuAY6IGXmYRCr3z932Y....w\..==\n-----END NEBULA CERTIFICATE-----\n",
|
||||
"PublicHostname": "",
|
||||
"PublicKey": "-----BEGIN NEBULA X25519 PRIVATE KEY-----\nnS39dWX...hTvP......+43thNrMu7U=\n-----END NEBULA X25519 PRIVATE KEY-----\n"
|
||||
},
|
||||
"status": "OK"
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function restart() {
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve({
|
||||
"status": "ok",
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function reset() {
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve({
|
||||
"status": "ok",
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function getConfig() {
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve({
|
||||
"data": "pki:\n ca: /config/ca.crt\n cert: /config/cosmos.crt\n key: /config/cosmos.key\n blocklist: []\nstatic_host_map:\n 192.168.201.1:\n - vpn.domain.com:4242\nlighthouse:\n am_lighthouse: true\n interval: 60\n hosts: []\nlisten:\n host: 0.0.0.0\n port: 4242\npunchy:\n punch: true\n respond: true\nrelay:\n am_relay: true\n use_relays: true\n relays: []\ntun:\n disabled: false\n dev: nebula1\n drop_local_broadcast: false\n drop_multicast: false\n tx_queue: 500\n mtu: 1300\n routes: []\n unsafe_routes: []\nlogging:\n level: info\n format: text\nfirewall:\n outbound_action: drop\n inbound_action: drop\n conntrack:\n tcp_timeout: 12m\n udp_timeout: 3m\n default_timeout: 10m\n outbound:\n - port: any\n proto: any\n host: any\n inbound:\n - port: any\n proto: any\n host: any\n",
|
||||
"status": "OK"
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function getLogs() {
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve({
|
||||
"data": "Some logs...",
|
||||
"status": "OK"
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function connect(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve({
|
||||
"status": "ok",
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function block(nickname, devicename, block) {
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve({
|
||||
"status": "ok",
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
export {
|
||||
list,
|
||||
addDevice,
|
||||
restart,
|
||||
getConfig,
|
||||
getLogs,
|
||||
reset,
|
||||
connect,
|
||||
block,
|
||||
};
|
110
client/src/api/constellation.tsx
Normal file
110
client/src/api/constellation.tsx
Normal file
|
@ -0,0 +1,110 @@
|
|||
import wrap from './wrap';
|
||||
|
||||
function list() {
|
||||
return wrap(fetch('/cosmos/api/constellation/devices', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
function addDevice(device) {
|
||||
return wrap(fetch('/cosmos/api/constellation/devices', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(device),
|
||||
}))
|
||||
}
|
||||
|
||||
function restart() {
|
||||
return wrap(fetch('/cosmos/api/constellation/restart', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
function reset() {
|
||||
return wrap(fetch('/cosmos/api/constellation/reset', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
function getConfig() {
|
||||
return wrap(fetch('/cosmos/api/constellation/config', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
function getLogs() {
|
||||
return wrap(fetch('/cosmos/api/constellation/logs', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
function connect(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.onload = () => {
|
||||
fetch('/cosmos/api/constellation/connect', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'text/plain',
|
||||
},
|
||||
body: reader.result,
|
||||
})
|
||||
.then(response => {
|
||||
// Add additional response handling here if needed.
|
||||
resolve(response);
|
||||
})
|
||||
.catch(error => {
|
||||
// Handle the error.
|
||||
reject(error);
|
||||
});
|
||||
};
|
||||
|
||||
reader.onerror = () => {
|
||||
reject(new Error('Failed to read the file.'));
|
||||
};
|
||||
|
||||
reader.readAsText(file);
|
||||
});
|
||||
}
|
||||
|
||||
function block(nickname, devicename, block) {
|
||||
return wrap(fetch(`/cosmos/api/constellation/block`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
nickname, devicename, block
|
||||
}),
|
||||
}))
|
||||
}
|
||||
|
||||
export {
|
||||
list,
|
||||
addDevice,
|
||||
restart,
|
||||
getConfig,
|
||||
getLogs,
|
||||
reset,
|
||||
connect,
|
||||
block,
|
||||
};
|
|
@ -361,7 +361,74 @@
|
|||
],
|
||||
"ServerCountry": "",
|
||||
"RequireMFA": false,
|
||||
"AutoUpdate": false
|
||||
"AutoUpdate": false,
|
||||
"ConstellationConfig": {
|
||||
"Enabled": true,
|
||||
"SlaveMode": false,
|
||||
"PrivateNode": false,
|
||||
"DNSDisabled": false,
|
||||
"DNSPort": "",
|
||||
"DNSFallback": "8.8.8.8:53",
|
||||
"DNSBlockBlacklist": true,
|
||||
"DNSAdditionalBlocklists": [
|
||||
"https://s3.amazonaws.com/lists.disconnect.me/simple_tracking.txt",
|
||||
"https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt"
|
||||
],
|
||||
"CustomDNSEntries": [],
|
||||
"NebulaConfig": {
|
||||
"PKI": {
|
||||
"CA": "",
|
||||
"Cert": "",
|
||||
"Key": "",
|
||||
"Blocklist": null
|
||||
},
|
||||
"StaticHostMap": null,
|
||||
"Lighthouse": {
|
||||
"AMLighthouse": false,
|
||||
"Interval": 0,
|
||||
"Hosts": null
|
||||
},
|
||||
"Listen": {
|
||||
"Host": "",
|
||||
"Port": 0
|
||||
},
|
||||
"Punchy": {
|
||||
"Punch": false,
|
||||
"Respond": false
|
||||
},
|
||||
"Relay": {
|
||||
"AMRelay": true,
|
||||
"UseRelays": false,
|
||||
"Relays": null
|
||||
},
|
||||
"TUN": {
|
||||
"Disabled": false,
|
||||
"Dev": "",
|
||||
"DropLocalBroadcast": false,
|
||||
"DropMulticast": false,
|
||||
"TxQueue": 0,
|
||||
"MTU": 0,
|
||||
"Routes": null,
|
||||
"UnsafeRoutes": null
|
||||
},
|
||||
"Logging": {
|
||||
"Level": "",
|
||||
"Format": ""
|
||||
},
|
||||
"Firewall": {
|
||||
"OutboundAction": "",
|
||||
"InboundAction": "",
|
||||
"Conntrack": {
|
||||
"TCPTimeout": "",
|
||||
"UDPTimeout": "",
|
||||
"DefaultTimeout": ""
|
||||
},
|
||||
"Outbound": null,
|
||||
"Inbound": null
|
||||
}
|
||||
},
|
||||
"ConstellationHostname": "vpn.domain.com"
|
||||
}
|
||||
},
|
||||
"updates": {
|
||||
"/Sonarr": true,
|
||||
|
|
28
client/src/api/downloadButton.jsx
Normal file
28
client/src/api/downloadButton.jsx
Normal file
|
@ -0,0 +1,28 @@
|
|||
import { Button } from "@mui/material";
|
||||
|
||||
export const DownloadFile = ({ filename, content, label }) => {
|
||||
const downloadFile = () => {
|
||||
// Create a blob with the content
|
||||
const blob = new Blob([content], { type: "text/plain;charset=utf-8" });
|
||||
|
||||
// Create a link element
|
||||
const link = document.createElement("a");
|
||||
link.href = URL.createObjectURL(blob);
|
||||
link.download = filename;
|
||||
|
||||
// Append the link to the document (needed for Firefox)
|
||||
document.body.appendChild(link);
|
||||
|
||||
// Simulate a click to start the download
|
||||
link.click();
|
||||
|
||||
// Cleanup the DOM by removing the link element
|
||||
document.body.removeChild(link);
|
||||
}
|
||||
|
||||
return (
|
||||
<Button onClick={downloadFile}>
|
||||
{label}
|
||||
</Button>
|
||||
);
|
||||
}
|
|
@ -3,6 +3,7 @@ import * as _users from './users';
|
|||
import * as _config from './config';
|
||||
import * as _docker from './docker';
|
||||
import * as _market from './market';
|
||||
import * as _constellation from './constellation';
|
||||
|
||||
import * as authDemo from './authentication.demo';
|
||||
import * as usersDemo from './users.demo';
|
||||
|
@ -10,6 +11,7 @@ import * as configDemo from './config.demo';
|
|||
import * as dockerDemo from './docker.demo';
|
||||
import * as indexDemo from './index.demo';
|
||||
import * as marketDemo from './market.demo';
|
||||
import * as constellationDemo from './constellation.demo';
|
||||
|
||||
import wrap from './wrap';
|
||||
import { redirectToLocal } from '../utils/indexs';
|
||||
|
@ -211,6 +213,7 @@ let users = _users;
|
|||
let config = _config;
|
||||
let docker = _docker;
|
||||
let market = _market;
|
||||
let constellation = _constellation;
|
||||
|
||||
if(isDemo) {
|
||||
auth = authDemo;
|
||||
|
@ -224,6 +227,7 @@ if(isDemo) {
|
|||
checkHost = indexDemo.checkHost;
|
||||
getDNS = indexDemo.getDNS;
|
||||
uploadBackground = indexDemo.uploadBackground;
|
||||
constellation = constellationDemo;
|
||||
}
|
||||
|
||||
export {
|
||||
|
@ -232,6 +236,7 @@ export {
|
|||
config,
|
||||
docker,
|
||||
market,
|
||||
constellation,
|
||||
getStatus,
|
||||
newInstall,
|
||||
isOnline,
|
||||
|
|
|
@ -4,6 +4,38 @@ function list() {
|
|||
resolve({
|
||||
"data": {
|
||||
"showcase": [
|
||||
{
|
||||
"name": "Home Assistant",
|
||||
"description": "Home Assistant is an open-source home automation platform that focuses on privacy and local control. It allows you to control all your devices from a single interface, integrating with a large number of devices and services. Home Assistant offers advanced automation capabilities, running perfectly on a Raspberry Pi or a local server. Start using Home Assistant today for a comprehensive home automation solution.",
|
||||
"url": "",
|
||||
"longDescription": "<p>Home Assistant is an open-source home automation platform focused on privacy and local control. It allows you to control all your devices from a single, unified interface. It is powered by a worldwide community of tinkerers and DIY enthusiasts, perfect to run on a Raspberry Pi or a local server.</p><p>Home Assistant integrates with a large number of different devices and services, providing the user with a powerful and flexible home automation control center. In addition to its powerful, flexible features, it provides advanced automation capabilities to help make your life easier.</p><p>With support for a vast array of devices and services, Home Assistant can be the one-stop solution for all your home automation needs. Get started with Home Assistant today and take control of your home automation!</p>",
|
||||
"tags": [
|
||||
"smart home",
|
||||
"home automation",
|
||||
"IoT",
|
||||
"Raspberry Pi",
|
||||
"local server",
|
||||
"privacy",
|
||||
"control",
|
||||
"automation",
|
||||
"devices",
|
||||
"services",
|
||||
"home assistant"
|
||||
],
|
||||
"repository": "https://github.com/home-assistant/core",
|
||||
"image": "https://hub.docker.com/r/homeassistant/home-assistant",
|
||||
"screenshots": [
|
||||
"https://azukaar.github.io/cosmos-servapps-official/servapps/HomeAssistant/screenshots/1.png",
|
||||
"https://azukaar.github.io/cosmos-servapps-official/servapps/HomeAssistant/screenshots/2.jpg",
|
||||
"https://azukaar.github.io/cosmos-servapps-official/servapps/HomeAssistant/screenshots/3.png"
|
||||
],
|
||||
"icon": "https://azukaar.github.io/cosmos-servapps-official/servapps/HomeAssistant/icon.png",
|
||||
"compose": "https://azukaar.github.io/cosmos-servapps-official/servapps/HomeAssistant/cosmos-compose.json",
|
||||
"supported_architectures": [
|
||||
"amd64",
|
||||
"arm64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Jellyfin",
|
||||
"description": "Jellyfin is a Free Software Media System that puts you in control of managing and streaming your media. It is an alternative to the proprietary Plex, to provide media from a dedicated server to end-user devices via multiple apps.",
|
||||
|
@ -93,66 +125,40 @@ function list() {
|
|||
"amd64",
|
||||
"arm64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Plex",
|
||||
"description": "Plex organizes all of your video, and music collections, and gives you instant access to them on all of your devices. With the free Plex Media Server software on your home computer and Plex for iOS, you can enjoy all of your personal media on your iPhone, iPad or iPod touch, and easily share it with friends and family. Plex also makes your media look beautiful with rich descriptions, artwork, and other related information. With an optional premium Plex Pass subscription, you can even sync videos, music, and photos to your smartphones and tablets to enjoy while offline. Parental controls, premium music features, music videos, trailers and extras, and powerful management tools are also part of our premium offering. Getting up and running is fast and simple, so get started now!",
|
||||
"url": "",
|
||||
"longDescription": "<p>Plex is a comprehensive media solution that organizes your video and music collections, giving you instant access across all your devices. With the free Plex Media Server software installed on your home computer and Plex's iOS app, you can enjoy your personal media on your iPhone, iPad, or iPod touch, and conveniently share it with your friends and family.</p><p>Plex is not just about easy access, it also enhances your media collection by adding rich descriptions, artwork, and other related information, making your media look visually appealing. If you choose to subscribe to the optional premium Plex Pass, you get the added ability to sync videos, music, and photos to your smartphones and tablets for offline enjoyment.</p><p>Premium features also include parental controls, enhanced music features, access to music videos, trailers, extras, and robust management tools. Starting with Plex is straightforward and quick. Get started today and transform your media experience!</p>",
|
||||
"tags": [
|
||||
"media",
|
||||
"movies",
|
||||
"streaming",
|
||||
"tv",
|
||||
"music",
|
||||
"photos",
|
||||
"videos",
|
||||
"audiobooks",
|
||||
"podcasts",
|
||||
"dlna",
|
||||
"chromecast",
|
||||
"android",
|
||||
"ios",
|
||||
"roku",
|
||||
"firetv",
|
||||
"smarttv",
|
||||
"web",
|
||||
"browser",
|
||||
"kodi",
|
||||
"emby",
|
||||
"plex",
|
||||
"media browser",
|
||||
"media server",
|
||||
"media streaming",
|
||||
"media player",
|
||||
"media center",
|
||||
"media management",
|
||||
"media organizer",
|
||||
"media collection",
|
||||
"media library",
|
||||
"media manager",
|
||||
"media sharing",
|
||||
"media transcoding",
|
||||
"media casting",
|
||||
"media casting",
|
||||
"media casting"
|
||||
],
|
||||
"repository": "https://github.com/plex/plex",
|
||||
"image": "https://hub.docker.com/r/linuxserver/plex",
|
||||
"screenshots": [
|
||||
"https://azukaar.github.io/cosmos-servapps-official/servapps/Plex/screenshots/1.png",
|
||||
"https://azukaar.github.io/cosmos-servapps-official/servapps/Plex/screenshots/2.png"
|
||||
],
|
||||
"icon": "https://azukaar.github.io/cosmos-servapps-official/servapps/Plex/icon.png",
|
||||
"compose": "https://azukaar.github.io/cosmos-servapps-official/servapps/Plex/cosmos-compose.json",
|
||||
"supported_architectures": [
|
||||
"amd64",
|
||||
"arm64"
|
||||
]
|
||||
}
|
||||
],
|
||||
"all": {
|
||||
"cosmos-cloud": [
|
||||
{
|
||||
"name": "Audiobookshelf",
|
||||
"description": "Audiobookshelf is a self-hosted audiobook and podcast server.",
|
||||
"url": "",
|
||||
"longDescription": "<p>Audiobookshelf is an open-source, self-hosted, audiobook and podcast server. It features an Android and iOS app for streaming your library, multi-user support, storing progress per user, and syncing across devices.</p><p>It also includes various audiobook management services like metadata fetching, chapter editing tools, and media merging.</p>",
|
||||
"tags": [
|
||||
"e-book",
|
||||
"library",
|
||||
"audiobook",
|
||||
"open-source",
|
||||
"management",
|
||||
"audiobookshelf",
|
||||
"docker",
|
||||
"linux",
|
||||
"ios",
|
||||
"android"
|
||||
],
|
||||
"repository": "https://github.com/advplyr/audiobookshelf",
|
||||
"image": "https://hub.docker.com/r/advplyr/audiobookshelf",
|
||||
"screenshots": [
|
||||
"https://azukaar.github.io/cosmos-servapps-official/servapps/Audiobookshelf/screenshots/1.png",
|
||||
"https://azukaar.github.io/cosmos-servapps-official/servapps/Audiobookshelf/screenshots/2.png"
|
||||
],
|
||||
"icon": "https://azukaar.github.io/cosmos-servapps-official/servapps/Audiobookshelf/icon.png",
|
||||
"compose": "https://azukaar.github.io/cosmos-servapps-official/servapps/Audiobookshelf/cosmos-compose.json",
|
||||
"supported_architectures": [
|
||||
"amd64",
|
||||
"arm64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Bazarr",
|
||||
"description": "Bazarr is an automated subtitle download application designed to complement Sonarr and Radarr. It simplifies the process of finding and downloading subtitles in various languages. Bazarr supports Windows, Linux, MacOS and provides a mobile-friendly web interface. If you're using Sonarr and Radarr, Bazarr can enhance your media management experience.",
|
||||
|
@ -350,6 +356,29 @@ function list() {
|
|||
"arm64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Dozzle",
|
||||
"description": "Dozzle is a real-time log viewer for docker containers.",
|
||||
"url": "",
|
||||
"longDescription": "<p>Dozzle is a small lightweight application with a web based interface to monitor Docker logs. It doesn’t store any log files. It is for live monitoring of your container logs only.</p>",
|
||||
"tags": [
|
||||
"dozzle",
|
||||
"open-source",
|
||||
"self-hosted",
|
||||
"log viewer"
|
||||
],
|
||||
"repository": "https://github.com/amir20/dozzle",
|
||||
"image": "https://hub.docker.com/r/amir20/dozzle",
|
||||
"screenshots": [
|
||||
"https://azukaar.github.io/cosmos-servapps-official/servapps/Dozzle/screenshots/1.png"
|
||||
],
|
||||
"icon": "https://azukaar.github.io/cosmos-servapps-official/servapps/Dozzle/icon.png",
|
||||
"compose": "https://azukaar.github.io/cosmos-servapps-official/servapps/Dozzle/cosmos-compose.json",
|
||||
"supported_architectures": [
|
||||
"amd64",
|
||||
"arm64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Duplicati",
|
||||
"description": "Duplicati is an open-source backup software that creates, encrypts, and stores backups of your files. It offers AES-256 encryption, incremental backups, scheduling, automated backups, and backup verification. Duplicati is compatible with Windows, Linux, MacOS, and Docker.",
|
||||
|
@ -383,6 +412,65 @@ function list() {
|
|||
"arm64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Emby",
|
||||
"description": "Emby(https://emby.media/) organizes video, music, live TV, and photos from personal media libraries and streams them to smart TVs, streaming boxes and mobile devices. This container is packaged as a standalone emby Media Server.",
|
||||
"url": "",
|
||||
"longDescription": "<p>Emby is an open-source media system that gives you the power to control the management and streaming of your media content. It offers an alternative to proprietary systems like Plex, empowering users to deliver media content from a dedicated server to various end-user devices through a range of apps.</p><p>Key features of Emby include the ability to stream movies, TV shows, music, photos, videos, audiobooks, and podcasts. It also supports various technologies and platforms such as DLNA, Chromecast, Android, iOS, Roku, FireTV, SmartTV, Web browser and Kodi. Emby essentially functions as a comprehensive media browser, server, streaming system, player, center, manager, organizer, and library. In addition to these, it also facilitates media sharing, transcoding, and casting, offering a robust solution for your media needs.</p>",
|
||||
"tags": [
|
||||
"media",
|
||||
"server",
|
||||
"streaming",
|
||||
"movies",
|
||||
"tv",
|
||||
"music",
|
||||
"photos",
|
||||
"videos",
|
||||
"audiobooks",
|
||||
"podcasts",
|
||||
"dlna",
|
||||
"chromecast",
|
||||
"android",
|
||||
"ios",
|
||||
"roku",
|
||||
"firetv",
|
||||
"smarttv",
|
||||
"web",
|
||||
"browser",
|
||||
"kodi",
|
||||
"emby",
|
||||
"plex",
|
||||
"media browser",
|
||||
"media server",
|
||||
"media streaming",
|
||||
"media player",
|
||||
"media center",
|
||||
"media management",
|
||||
"media organizer",
|
||||
"media collection",
|
||||
"media library",
|
||||
"media manager",
|
||||
"media sharing",
|
||||
"media transcoding",
|
||||
"media casting",
|
||||
"media casting",
|
||||
"media casting"
|
||||
],
|
||||
"repository": "https://github.com/linuxserver/docker-emby",
|
||||
"image": "https://hub.docker.com/r/linuxserver/emby",
|
||||
"screenshots": [
|
||||
"https://azukaar.github.io/cosmos-servapps-official/servapps/Emby/screenshots/1.png",
|
||||
"https://azukaar.github.io/cosmos-servapps-official/servapps/Emby/screenshots/2.png",
|
||||
"https://azukaar.github.io/cosmos-servapps-official/servapps/Emby/screenshots/3.png",
|
||||
"https://azukaar.github.io/cosmos-servapps-official/servapps/Emby/screenshots/4.png"
|
||||
],
|
||||
"icon": "https://azukaar.github.io/cosmos-servapps-official/servapps/Emby/icon.png",
|
||||
"compose": "https://azukaar.github.io/cosmos-servapps-official/servapps/Emby/cosmos-compose.json",
|
||||
"supported_architectures": [
|
||||
"amd64",
|
||||
"arm64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Emulator-JS",
|
||||
"description": "Emulator-JS is an open-source, in-browser multi-system emulator. It allows users to play games from various console systems directly in the browser, facilitated by an intuitive ROM management system. Emulator-JS can be used on any platform that supports a modern web browser, such as Windows, Linux, MacOS, and mobile devices.",
|
||||
|
@ -417,6 +505,39 @@ function list() {
|
|||
"arm64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Filebrowser",
|
||||
"description": "Filebrowser is an open-source, self-hosted web application for managing files within your server. It offers features like custom commands, user and permissions management, shareable links, built-in text editor, and media file previews. Filebrowser can be run on any platform that supports Go, including various Linux distributions, Windows, MacOS, and Docker.",
|
||||
"url": "",
|
||||
"longDescription": "<p>Filebrowser is an open-source, self-hosted web application for managing files within your server's file system. The application provides a user-friendly interface, making it simple to navigate, upload, download, and manage your files.</p><p>Key features of Filebrowser include support for custom commands, user and permissions management, shareable links, and a built-in text editor. It also supports various media file previews, including video and image file formats.</p><p>Filebrowser is designed to run on any platform that supports Go, including various Linux distributions, Windows, and MacOS. Additionally, it can be easily deployed using Docker.</p>",
|
||||
"tags": [
|
||||
"filebrowser",
|
||||
"open-source",
|
||||
"self-hosted",
|
||||
"file management",
|
||||
"custom commands",
|
||||
"permissions management",
|
||||
"shareable links",
|
||||
"text editor",
|
||||
"media previews",
|
||||
"go",
|
||||
"linux",
|
||||
"windows",
|
||||
"macos",
|
||||
"docker"
|
||||
],
|
||||
"repository": "https://github.com/filebrowser/filebrowser",
|
||||
"image": "https://hub.docker.com/r/filebrowser/filebrowser",
|
||||
"screenshots": [
|
||||
"https://azukaar.github.io/cosmos-servapps-official/servapps/Filebrowser/screenshots/1.gif"
|
||||
],
|
||||
"icon": "https://azukaar.github.io/cosmos-servapps-official/servapps/Filebrowser/icon.png",
|
||||
"compose": "https://azukaar.github.io/cosmos-servapps-official/servapps/Filebrowser/cosmos-compose.json",
|
||||
"supported_architectures": [
|
||||
"amd64",
|
||||
"arm64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "FreshRSS",
|
||||
"description": "FreshRSS is an open-source, self-hosted RSS feed aggregator that is lightweight and easy to use. Features include a responsive design, import/export OPML files, multiple themes, filters, categories, multi-user support, and extensibility with plugins. FreshRSS is compatible with Windows, Linux, MacOS, and Docker.",
|
||||
|
@ -545,6 +666,28 @@ function list() {
|
|||
"arm64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Handbrake",
|
||||
"description": "HandBrake is a tool for converting video from nearly any format to a selection of modern, widely supported codecs.",
|
||||
"url": "",
|
||||
"longDescription": "<p>This project implements a Docker container for <a href=https://handbrake.fr/ target=_blank>HandBrake</a>.</p><p>The GUI of the application is accessed through a modern web browser (no installation or configuration needed on the client side) or via any VNC client.</p><p>A fully automated mode is also available: drop files into a watch folder and let HandBrake process them without any user interaction.</p><p>Full documentation is available at <a href=https://github.com/jlesage/docker-handbrake target=_blank>https://github.com/jlesage/docker-handbrake</a>.</p>",
|
||||
"tags": [
|
||||
"media",
|
||||
"video",
|
||||
"conversion"
|
||||
],
|
||||
"repository": "https://github.com/jlesage/docker-handbrake",
|
||||
"image": "https://hub.docker.com/r/jlesage/handbrake",
|
||||
"screenshots": [
|
||||
"https://azukaar.github.io/cosmos-servapps-official/servapps/Handbrake/screenshots/1.jpg",
|
||||
"https://azukaar.github.io/cosmos-servapps-official/servapps/Handbrake/screenshots/2.jpg"
|
||||
],
|
||||
"icon": "https://azukaar.github.io/cosmos-servapps-official/servapps/Handbrake/icon.png",
|
||||
"compose": "https://azukaar.github.io/cosmos-servapps-official/servapps/Handbrake/cosmos-compose.json",
|
||||
"supported_architectures": [
|
||||
"amd64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Heimdall",
|
||||
"description": "Heimdall is an open-source, self-hosted dashboard software that provides a central hub for web-based applications and services. Features include a built-in application library, custom themes, multi-user support, and compatibility with desktop and mobile browsers. Heimdall is compatible with Windows, Linux, MacOS, and Docker.",
|
||||
|
@ -579,6 +722,39 @@ function list() {
|
|||
"arm64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Homarr",
|
||||
"description": "Homarr is an open-source, self-hosted dashboard software that provides a central hub for web-based applications and services. Features include a built-in application library, custom themes, multi-user support, and compatibility with desktop and mobile browsers. Homarr is compatible with Windows, Linux, MacOS, and Docker.",
|
||||
"url": "",
|
||||
"longDescription": "<p>Homarr is an open-source, self-hosted dashboard software that allows you to organize and access your web-based applications and services all in one place. It's designed to provide a central hub to simplify your web environment.</p><p>Key features of Homarr include the ability to add applications via a built-in library or by creating custom application definitions, custom themes, and the capacity to run on a desktop or mobile browser. It supports multiple users and each user can have their own personalized set of applications.</p><p>Homarr is compatible with various platforms, including Windows, Linux, MacOS, and it supports Docker, making it a highly versatile tool for various environments.</p>",
|
||||
"tags": [
|
||||
"dashboard",
|
||||
"home",
|
||||
"icons",
|
||||
"open-source",
|
||||
"self-hosted",
|
||||
"web-based applications",
|
||||
"services",
|
||||
"central hub",
|
||||
"multi-user",
|
||||
"windows",
|
||||
"linux",
|
||||
"macos",
|
||||
"docker"
|
||||
],
|
||||
"repository": "https://github.com/ajnart/homarr",
|
||||
"image": "https://hub.docker.com/r/homarr",
|
||||
"screenshots": [
|
||||
"https://azukaar.github.io/cosmos-servapps-official/servapps/Homarr/screenshots/1.jpg",
|
||||
"https://azukaar.github.io/cosmos-servapps-official/servapps/Homarr/screenshots/2.webp"
|
||||
],
|
||||
"icon": "https://azukaar.github.io/cosmos-servapps-official/servapps/Homarr/icon.png",
|
||||
"compose": "https://azukaar.github.io/cosmos-servapps-official/servapps/Homarr/cosmos-compose.json",
|
||||
"supported_architectures": [
|
||||
"amd64",
|
||||
"arm64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Home Assistant",
|
||||
"description": "Home Assistant is an open-source home automation platform that focuses on privacy and local control. It allows you to control all your devices from a single interface, integrating with a large number of devices and services. Home Assistant offers advanced automation capabilities, running perfectly on a Raspberry Pi or a local server. Start using Home Assistant today for a comprehensive home automation solution.",
|
||||
|
@ -611,6 +787,34 @@ function list() {
|
|||
"arm64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Homebox",
|
||||
"description": "Homebox is the inventory and organization system built for the Home User.",
|
||||
"url": "",
|
||||
"longDescription": "<p>Homebox is the inventory and organization system built for the Home User! With a focus on simplicity and ease of use, Homebox is the perfect solution for your home inventory, organization, and management needs.</p>",
|
||||
"tags": [
|
||||
"home",
|
||||
"homebox",
|
||||
"organization",
|
||||
"inventory",
|
||||
"management",
|
||||
"docker",
|
||||
"linux"
|
||||
],
|
||||
"repository": "https://github.com/hay-kot/homebox",
|
||||
"image": "https://ghcr.io/hay-kot/homebox:latest",
|
||||
"screenshots": [
|
||||
"https://azukaar.github.io/cosmos-servapps-official/servapps/Homebox/screenshots/1.png",
|
||||
"https://azukaar.github.io/cosmos-servapps-official/servapps/Homebox/screenshots/2.png",
|
||||
"https://azukaar.github.io/cosmos-servapps-official/servapps/Homebox/screenshots/3.png"
|
||||
],
|
||||
"icon": "https://azukaar.github.io/cosmos-servapps-official/servapps/Homebox/icon.png",
|
||||
"compose": "https://azukaar.github.io/cosmos-servapps-official/servapps/Homebox/cosmos-compose.json",
|
||||
"supported_architectures": [
|
||||
"amd64",
|
||||
"arm64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Immich",
|
||||
"description": "Immich - High performance self-hosted photo and video backup solution",
|
||||
|
@ -703,6 +907,45 @@ function list() {
|
|||
"arm64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Jellyseerr",
|
||||
"description": "Jellyseerr is a free and open source software application for managing requests for your media library. It is a a fork of Overseerr built to bring support for Jellyfin & Emby media servers!",
|
||||
"url": "",
|
||||
"longDescription": "<p>Jellyseerr is a free and open source software application for managing requests for your media library. It is a a fork of Overseerr built to bring support for Jellyfin & Emby media servers!</p>",
|
||||
"tags": [
|
||||
"media",
|
||||
"request",
|
||||
"library",
|
||||
"open-source",
|
||||
"self-hosted",
|
||||
"web application",
|
||||
"plex",
|
||||
"emby",
|
||||
"jellyfin",
|
||||
"request system",
|
||||
"media content",
|
||||
"windows",
|
||||
"linux",
|
||||
"macos",
|
||||
"docker",
|
||||
"radarr",
|
||||
"sonarr"
|
||||
],
|
||||
"repository": "https://github.com/Fallenbagel/jellyseerr",
|
||||
"image": "https://hub.docker.com/r/fallenbagel/jellyseerr",
|
||||
"screenshots": [
|
||||
"https://azukaar.github.io/cosmos-servapps-official/servapps/Jellyseerr/screenshots/1.png",
|
||||
"https://azukaar.github.io/cosmos-servapps-official/servapps/Jellyseerr/screenshots/2.png",
|
||||
"https://azukaar.github.io/cosmos-servapps-official/servapps/Jellyseerr/screenshots/3.png",
|
||||
"https://azukaar.github.io/cosmos-servapps-official/servapps/Jellyseerr/screenshots/4.png"
|
||||
],
|
||||
"icon": "https://azukaar.github.io/cosmos-servapps-official/servapps/Jellyseerr/icon.png",
|
||||
"compose": "https://azukaar.github.io/cosmos-servapps-official/servapps/Jellyseerr/cosmos-compose.json",
|
||||
"supported_architectures": [
|
||||
"amd64",
|
||||
"arm64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Joplin",
|
||||
"description": "Joplin is a free, open-source note-taking and to-do application that supports markdown and end-to-end encryption. It offers capabilities such as tagging, searching, and modifying notes, and can sync with various cloud platforms. It features an extensible plugin system, allowing for tailored functionality. Joplin is compatible across multiple platforms including Windows, Linux, MacOS, iOS, and Android.",
|
||||
|
@ -762,6 +1005,31 @@ function list() {
|
|||
"arm64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "LSDVR",
|
||||
"description": "Automatic VOD recording",
|
||||
"url": "",
|
||||
"longDescription": "Automatic VOD recording around when the stream goes live, instead of checking it every minute like many other scripts do. Because of notification delays, the stream usually starts capturing after ~2 minutes after the stream goes live. Cyclic recording, as in when a specified amount or storage per streamer is reached, the oldest stream gets deleted. Tons of metadata, maybe too much. Stores info about games played, stream titles, duration, if the stream got muted from copyrighted music, etc. Viewer count logging with graphs. Chapters (titles and games) are written to the final video file. <a href='https://github.com/MrBrax/twitch-vod-chat'>Video player</a> with chat playback. Video cutter with chapter display for easy exporting, also cuts the downloaded chat for synced rendering. Notifications with optional speech when the website is open, get stream live notifications far earlier than the mobile app does. Writes a <a href='https://github.com/mifi/lossless-cut/'>losslesscut</a> compatible csv file for the full VOD, so you don't have to find all the games. Uses `ts` instead of `mp4` so if the stream or program crashes, the file won't be corrupted. Audio only support. Optionally either dumps chat while capturing or downloads the chat file after it's done. Basic tools for downloading any VOD, chat, or clip. Can be set to automatically download the whole stream chat to a JSON file, to be used in my <a href='https://github.com/MrBrax/twitch-vod-chat'>twitch-vod-chat</a> webapp or automatically burned in with <a href='https://github.com/lay295/TwitchDownloader'>TwitchDownloader</a>. Basic webhook support for external scripting. Notifications over the browser, telegram, pushover, and discord. Mobile friendly site with PWA. Exporting of videos to external file, SFTP, and YouTube. Can be enabled for all finished captures Can be run for an entire channel at once</p>",
|
||||
"tags": [
|
||||
"twitch",
|
||||
"youtube",
|
||||
"download",
|
||||
"videos",
|
||||
"vod",
|
||||
"media"
|
||||
],
|
||||
"repository": "https://github.com/alexta69/metube",
|
||||
"image": "https://github.com/alexta69/metube/pkgs/container/metube",
|
||||
"screenshots": [
|
||||
"https://azukaar.github.io/cosmos-servapps-official/servapps/LSDVR/screenshots/1.png"
|
||||
],
|
||||
"icon": "https://azukaar.github.io/cosmos-servapps-official/servapps/LSDVR/icon.png",
|
||||
"compose": "https://azukaar.github.io/cosmos-servapps-official/servapps/LSDVR/cosmos-compose.json",
|
||||
"supported_architectures": [
|
||||
"amd64",
|
||||
"arm64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Lemmy",
|
||||
"description": "Lemmy is an open-source, self-hosted, federated link aggregator similar to Reddit. It offers community formation, moderation tools, content voting, and supports commenting and private messaging between users. Lemmy can be run on Windows, Linux, MacOS, and Docker.",
|
||||
|
@ -837,6 +1105,28 @@ function list() {
|
|||
"arm64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "MKVToolNix",
|
||||
"description": "MKVToolNix is a set of tools to create, alter and inspect Matroska files.",
|
||||
"url": "",
|
||||
"longDescription": "<p>This project implements a Docker container for MKVToolNix.</p><p>The GUI of the application is accessed through a modern web browser (no installation or configuration needed on the client side) or via any VNC client.</p>",
|
||||
"tags": [
|
||||
"media",
|
||||
"video",
|
||||
"conversion"
|
||||
],
|
||||
"repository": "https://github.com/jlesage/docker-mkvtoolnix",
|
||||
"image": "https://hub.docker.com/r/jlesage/mkvtoolnix",
|
||||
"screenshots": [
|
||||
"https://azukaar.github.io/cosmos-servapps-official/servapps/MKVToolNix/screenshots/1.jpeg",
|
||||
"https://azukaar.github.io/cosmos-servapps-official/servapps/MKVToolNix/screenshots/2.jpeg"
|
||||
],
|
||||
"icon": "https://azukaar.github.io/cosmos-servapps-official/servapps/MKVToolNix/icon.png",
|
||||
"compose": "https://azukaar.github.io/cosmos-servapps-official/servapps/MKVToolNix/cosmos-compose.json",
|
||||
"supported_architectures": [
|
||||
"amd64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Mastodon",
|
||||
"description": "Mastodon is a free and open-source self-hosted social networking service. It features decentralization, ability to build your own community, per-post privacy settings, anti-abuse tools, and supports multimedia attachments. Mastodon can be run on any platform that supports Ruby, Node.js, and PostgreSQL, including various Linux distributions and Docker.",
|
||||
|
@ -871,6 +1161,95 @@ function list() {
|
|||
"arm64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "MeTube",
|
||||
"description": "youtube-dl web UI",
|
||||
"url": "",
|
||||
"longDescription": "<p>Web GUI for youtube-dl (using the yt-dlp fork) with playlist support. Allows you to download videos from YouTube and dozens of other sites.</p>",
|
||||
"tags": [
|
||||
"youtube",
|
||||
"youtube-dl",
|
||||
"download",
|
||||
"videos",
|
||||
"music",
|
||||
"media"
|
||||
],
|
||||
"repository": "https://github.com/alexta69/metube",
|
||||
"image": "https://github.com/alexta69/metube/pkgs/container/metube",
|
||||
"screenshots": [
|
||||
"https://azukaar.github.io/cosmos-servapps-official/servapps/MeTube/screenshots/1.gif"
|
||||
],
|
||||
"icon": "https://azukaar.github.io/cosmos-servapps-official/servapps/MeTube/icon.png",
|
||||
"compose": "https://azukaar.github.io/cosmos-servapps-official/servapps/MeTube/cosmos-compose.json",
|
||||
"supported_architectures": [
|
||||
"amd64",
|
||||
"arm64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Mealie",
|
||||
"description": "Recipe Management For The Modern Household",
|
||||
"url": "",
|
||||
"longDescription": "<p>Mealie is an intuitive and easy to use recipe management app. It's designed to make your life easier by being the best recipes management experience on the web and providing you with an easy to use interface to manage your growing collection of recipes. </p><p>Default Password is: MyPassword</p>",
|
||||
"tags": [
|
||||
"recipes",
|
||||
"meals",
|
||||
"shopping",
|
||||
"planning",
|
||||
"cookbook",
|
||||
"docker",
|
||||
"pwa"
|
||||
],
|
||||
"repository": "https://github.com/mealie-recipes/mealie",
|
||||
"image": "https://hub.docker.com/r/hkotel/mealie/",
|
||||
"screenshots": [
|
||||
"https://azukaar.github.io/cosmos-servapps-official/servapps/Mealie/screenshots/1.png"
|
||||
],
|
||||
"icon": "https://azukaar.github.io/cosmos-servapps-official/servapps/Mealie/icon.png",
|
||||
"compose": "https://azukaar.github.io/cosmos-servapps-official/servapps/Mealie/cosmos-compose.json",
|
||||
"supported_architectures": [
|
||||
"amd64",
|
||||
"arm64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Minecraft",
|
||||
"description": "Minecraft is a sandbox video game developed by Mojang Studios that allows players to explore, interact with, and modify a dynamically-generated map made of blocks. Players can build structures, craft items, and interact with the game world in a variety of ways. The game supports multiple gameplay styles and modes, and is available on a wide range of platforms.",
|
||||
"url": "",
|
||||
"longDescription": "<p>Minecraft is a highly popular and critically acclaimed sandbox video game developed by Mojang Studios. It allows players to explore a blocky, procedurally generated 3D world with virtually infinite terrain, and may discover and extract raw materials, craft tools and items, build structures, and interact with the game's various entities.</p><p>Gameplay involves players interacting with the game world by placing and breaking various types of blocks in a three-dimensional environment. In this environment, players can build creative structures, creations, and artwork on multiplayer servers and singleplayer worlds across multiple game modes. Minecraft supports a wide range of gameplay styles, including survival mode, creative mode, adventure mode, and spectator mode.</p><p>Minecraft is available on multiple platforms, including Windows, MacOS, Linux, and various consoles and mobile devices.</p>",
|
||||
"tags": [
|
||||
"game servers",
|
||||
"mojang",
|
||||
"sandbox",
|
||||
"game",
|
||||
"procedurally generated",
|
||||
"3D",
|
||||
"multiplayer",
|
||||
"singleplayer",
|
||||
"survival",
|
||||
"creative",
|
||||
"adventure",
|
||||
"spectator",
|
||||
"windows",
|
||||
"macos",
|
||||
"linux",
|
||||
"console",
|
||||
"mobile"
|
||||
],
|
||||
"repository": "https://www.minecraft.net/",
|
||||
"image": "https://www.minecraft.net/en-us/about-minecraft",
|
||||
"screenshots": [
|
||||
"https://azukaar.github.io/cosmos-servapps-official/servapps/Minecraft-Server/screenshots/1.webp"
|
||||
],
|
||||
"icon": "https://azukaar.github.io/cosmos-servapps-official/servapps/Minecraft-Server/icon.png",
|
||||
"compose": "https://azukaar.github.io/cosmos-servapps-official/servapps/Minecraft-Server/cosmos-compose.json",
|
||||
"supported_architectures": [
|
||||
"x86",
|
||||
"amd64",
|
||||
"arm",
|
||||
"arm64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "NZBGet",
|
||||
"description": "NZBGet is a high-performance Usenet downloader that uses NZB files to fetch content. It is designed to use minimal system resources while offering features such as a web-interface, API, automatic post-processing, and RSS feed support. NZBGet works well on various platforms including low powered devices. Start using NZBGet for your Usenet downloads today!",
|
||||
|
@ -935,6 +1314,29 @@ function list() {
|
|||
"arm64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Notifiarr",
|
||||
"description": "You just found one of the coolest tools on the Internet for a homelab enthusiast. We do notifications.",
|
||||
"url": "",
|
||||
"longDescription": "<p>You just found one of the coolest tools on the Internet for a homelab enthusiast. We do notifications. We do them right. We've been doing then for years and we'll keep doing them for years to come. Notifiarr provides native custom integrations with dozens, maybe hundreds of applications and websites. That means these applications or websites can send data to Notifiarr, and we'll format a message according to your configuration then send it to your chat server.</p>",
|
||||
"tags": [
|
||||
"notifications",
|
||||
"rest api",
|
||||
"curl",
|
||||
"push notifications"
|
||||
],
|
||||
"repository": "https://github.com/Notifiarr/notifiarr",
|
||||
"image": "https://hub.docker.com/r/golift/notifiarr",
|
||||
"screenshots": [
|
||||
"https://azukaar.github.io/cosmos-servapps-official/servapps/Notifiarr/screenshots/1.png"
|
||||
],
|
||||
"icon": "https://azukaar.github.io/cosmos-servapps-official/servapps/Notifiarr/icon.png",
|
||||
"compose": "https://azukaar.github.io/cosmos-servapps-official/servapps/Notifiarr/cosmos-compose.json",
|
||||
"supported_architectures": [
|
||||
"amd64",
|
||||
"arm64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "OhMyForm",
|
||||
"description": "OhMyForm is a free, open-source form creation software. It offers a range of features for creating, administering, analyzing, and distributing beautiful, embeddable forms for various purposes such as recruiting, market research, and surveys. OhMyForm allows for self-hosting with no installation fees or monthly charges.",
|
||||
|
@ -995,6 +1397,70 @@ function list() {
|
|||
"arm64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Overseerr",
|
||||
"description": "Overseerr is a request management and media discovery tool built to work with your existing Plex ecosystem.",
|
||||
"url": "",
|
||||
"longDescription": "<p>Overseerr is a free and open source software application for managing requests for your media library. It integrates with your existing services, such as Sonarr, Radarr, and Plex.</p><p>Overseerr helps you find media you want to watch. With inline recommendations and suggestions, you will find yourself deeper and deeper in a rabbit hole of content you never knew you just had to have.</p><p>Overseerr presents you and your users with a request interface that is incredibly easy to understand and use. Users can select the exact seasons they want to watch. Advanced users can use the “Advanced Requests” options to change destination folders and quality profiles.</p><p>Overseerr aims to make you and your user's lives more effortless than ever before.</p>",
|
||||
"tags": [
|
||||
"media",
|
||||
"request",
|
||||
"library",
|
||||
"open-source",
|
||||
"self-hosted",
|
||||
"web application",
|
||||
"plex",
|
||||
"request system",
|
||||
"media content",
|
||||
"windows",
|
||||
"linux",
|
||||
"macos",
|
||||
"docker",
|
||||
"radarr",
|
||||
"sonarr"
|
||||
],
|
||||
"repository": "https://github.com/sct/overseerr",
|
||||
"image": "https://hub.docker.com/r/linuxserver/overseerr",
|
||||
"screenshots": [
|
||||
"https://azukaar.github.io/cosmos-servapps-official/servapps/Overseerr/screenshots/1.jpg",
|
||||
"https://azukaar.github.io/cosmos-servapps-official/servapps/Overseerr/screenshots/2.jpg",
|
||||
"https://azukaar.github.io/cosmos-servapps-official/servapps/Overseerr/screenshots/3.jpg"
|
||||
],
|
||||
"icon": "https://azukaar.github.io/cosmos-servapps-official/servapps/Overseerr/icon.png",
|
||||
"compose": "https://azukaar.github.io/cosmos-servapps-official/servapps/Overseerr/cosmos-compose.json",
|
||||
"supported_architectures": [
|
||||
"amd64",
|
||||
"arm64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Paperless-ngx",
|
||||
"description": "Paperless-ngx is a document management system that transforms your physical documents into a searchable online archive so you can keep, well, less paper.",
|
||||
"url": "",
|
||||
"longDescription": "<p>Paperless-ngx is a community-supported open-source document management system that transforms your physical documents into a searchable online archive so you can keep, well, less paper.</p><p>This version of Paperless-ngx is configured using PostgreSQL as the database server, as well as Apache, Tika, and Gotenberg servers to provide support for consuming Office documents (Word, Excel, Power Point and their LibreOffice counterparts.</p>",
|
||||
"tags": [
|
||||
"document",
|
||||
"management",
|
||||
"organization",
|
||||
"archive",
|
||||
"collection",
|
||||
"web interface",
|
||||
"paper",
|
||||
"docker"
|
||||
],
|
||||
"repository": "https://github.com/paperless-ngx/paperless-ngx",
|
||||
"image": "https://ghcr.io/paperless-ngx/paperless-ngx",
|
||||
"screenshots": [
|
||||
"https://azukaar.github.io/cosmos-servapps-official/servapps/Paperless-ngx/screenshots/1.png",
|
||||
"https://azukaar.github.io/cosmos-servapps-official/servapps/Paperless-ngx/screenshots/2.png"
|
||||
],
|
||||
"icon": "https://azukaar.github.io/cosmos-servapps-official/servapps/Paperless-ngx/icon.png",
|
||||
"compose": "https://azukaar.github.io/cosmos-servapps-official/servapps/Paperless-ngx/cosmos-compose.json",
|
||||
"supported_architectures": [
|
||||
"amd64",
|
||||
"arm64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "PhotoPrism",
|
||||
"description": "PhotoPrism is an open-source personal photo management tool that uses smart search, automated tagging, and geolocation to help organize your photos. It supports various image formats, generates thumbnails, and converts RAW images. PhotoPrism works across various platforms and offers a private, self-hosted solution for managing your photos. Start using PhotoPrism today for your photo management needs.",
|
||||
|
@ -1031,7 +1497,7 @@ function list() {
|
|||
"name": "Plex",
|
||||
"description": "Plex organizes all of your video, and music collections, and gives you instant access to them on all of your devices. With the free Plex Media Server software on your home computer and Plex for iOS, you can enjoy all of your personal media on your iPhone, iPad or iPod touch, and easily share it with friends and family. Plex also makes your media look beautiful with rich descriptions, artwork, and other related information. With an optional premium Plex Pass subscription, you can even sync videos, music, and photos to your smartphones and tablets to enjoy while offline. Parental controls, premium music features, music videos, trailers and extras, and powerful management tools are also part of our premium offering. Getting up and running is fast and simple, so get started now!",
|
||||
"url": "",
|
||||
"longDescription": "<p>Plex is a comprehensive media solution that organizes your video and music collections, giving you instant access across all your devices. With the free Plex Media Server software installed on your home computer and Plex's iOS app, you can enjoy your personal media on your iPhone, iPad, or iPod touch, and conveniently share it with your friends and family.</p><p>Plex is not just about easy access, it also enhances your media collection by adding rich descriptions, artwork, and other related information, making your media look visually appealing. If you choose to subscribe to the optional premium Plex Pass, you get the added ability to sync videos, music, and photos to your smartphones and tablets for offline enjoyment.</p><p>Premium features also include parental controls, enhanced music features, access to music videos, trailers, extras, and robust management tools. Starting with Plex is straightforward and quick. Get started today and transform your media experience!</p>",
|
||||
"longDescription": "<p>WARNING! We highly advise AGAINST using Plex. It is gradually becoming agressive against the self-hosting community to the point that it might as well become cloud only in the next few years. It is also NOT a selfhosted app as a lof of your data are in the cloud (include analytics about what you watch) and it is not opensource. Instead, consider using Jellyfin, which is selfhosted and opensource unlike Plex.</p><p>Plex is a comprehensive media solution that organizes your video and music collections, giving you instant access across all your devices. With the free Plex Media Server software installed on your home computer and Plex's iOS app, you can enjoy your personal media on your iPhone, iPad, or iPod touch, and conveniently share it with your friends and family.</p><p>Plex is not just about easy access, it also enhances your media collection by adding rich descriptions, artwork, and other related information, making your media look visually appealing. If you choose to subscribe to the optional premium Plex Pass, you get the added ability to sync videos, music, and photos to your smartphones and tablets for offline enjoyment.</p><p>Premium features also include parental controls, enhanced music features, access to music videos, trailers, extras, and robust management tools. Starting with Plex is straightforward and quick. Get started today and transform your media experience!</p>",
|
||||
"tags": [
|
||||
"media",
|
||||
"movies",
|
||||
|
@ -1153,6 +1619,45 @@ function list() {
|
|||
"arm64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Ryot",
|
||||
"description": "A self hosted platform for tracking various facets of your life - media, fitness etc.",
|
||||
"url": "",
|
||||
"longDescription": "<p>Ryot is a self hosted platform for tracking various facets of your life - media, fitness etc. Imagine you have a special notebook where you can write down all the media you have consumed, like books you've read, shows you have watched, video games you have played or workouts you have done. Now, imagine that instead of a physical notebook, you have a special tool on your computer or phone that lets you keep track of all these digitally.</p>",
|
||||
"tags": [
|
||||
"tracker",
|
||||
"media",
|
||||
"fitness",
|
||||
"open-source",
|
||||
"collection",
|
||||
"docker",
|
||||
"windows",
|
||||
"linux",
|
||||
"macos",
|
||||
"web interface",
|
||||
"metadata",
|
||||
"episode tracking",
|
||||
"movie tracking",
|
||||
"audiobook tracking",
|
||||
"reading tracking",
|
||||
"fitness tracking",
|
||||
"integrations",
|
||||
"ryot"
|
||||
],
|
||||
"repository": "https://github.com/IgnisDa/ryot",
|
||||
"image": "https://ghcr.io/ignisda/ryot",
|
||||
"screenshots": [
|
||||
"https://azukaar.github.io/cosmos-servapps-official/servapps/Ryot/screenshots/1.png",
|
||||
"https://azukaar.github.io/cosmos-servapps-official/servapps/Ryot/screenshots/2.png",
|
||||
"https://azukaar.github.io/cosmos-servapps-official/servapps/Ryot/screenshots/3.png"
|
||||
],
|
||||
"icon": "https://azukaar.github.io/cosmos-servapps-official/servapps/Ryot/icon.png",
|
||||
"compose": "https://azukaar.github.io/cosmos-servapps-official/servapps/Ryot/cosmos-compose.json",
|
||||
"supported_architectures": [
|
||||
"amd64",
|
||||
"arm64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Sonarr",
|
||||
"description": "Sonarr is an automated TV show manager that can search, download, and manage your TV series. It offers features like automatic quality upgrades, episode tracking, and metadata fetching. Sonarr works across various platforms including Windows, Linux, MacOS, and has a mobile-friendly web interface. Start using Sonarr now to automate your TV show management process.",
|
||||
|
@ -1250,6 +1755,44 @@ function list() {
|
|||
"arm64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Unmanic",
|
||||
"description": "Unmanic is a simple tool for optimising your file library. You can use it to convert your files into a single, uniform format, manage file movements based on timestamps, or execute custom commands against a file based on its file size.",
|
||||
"url": "",
|
||||
"longDescription": "<p>Unmanic is a simple tool for optimising your file library. You can use it to convert your files into a single, uniform format, manage file movements based on timestamps, or execute custom commands against a file based on its file size.</p><p>Simply configure Unmanic pointing it at your library and let it automatically manage that library for you.</p><p>Unmanic provides you with the following main functions:</p><ul><li>A scheduler built in to scan your whole library for files that do not conform to your configured file presets. Files found requiring processing are then queued.</li><li>A file/directory monitor. When a file is modified, or a new file is added in your library, Unmanic is able to again test that against your configured file presets. Like the first function, if this file requires processing, it is added to a queue for processing.</li><li>A handler to manage running multiple file manipulation tasks at a time.</li><li>A Web UI to easily configure, manage and monitor the progress of your library optimisation.</li></ul>",
|
||||
"tags": [
|
||||
"media",
|
||||
"server",
|
||||
"tv",
|
||||
"videos",
|
||||
"web",
|
||||
"browser",
|
||||
"media browser",
|
||||
"media server",
|
||||
"media streaming",
|
||||
"media player",
|
||||
"media center",
|
||||
"media management",
|
||||
"media organizer",
|
||||
"media collection",
|
||||
"media library",
|
||||
"media manager"
|
||||
],
|
||||
"repository": "https://github.com/Unmanic/unmanic",
|
||||
"image": "https://hub.docker.com/r/josh5/unmanic",
|
||||
"screenshots": [
|
||||
"https://azukaar.github.io/cosmos-servapps-official/servapps/Unmanic/screenshots/1.png",
|
||||
"https://azukaar.github.io/cosmos-servapps-official/servapps/Unmanic/screenshots/2.png",
|
||||
"https://azukaar.github.io/cosmos-servapps-official/servapps/Unmanic/screenshots/3.png"
|
||||
],
|
||||
"icon": "https://azukaar.github.io/cosmos-servapps-official/servapps/Unmanic/icon.png",
|
||||
"compose": "https://azukaar.github.io/cosmos-servapps-official/servapps/Unmanic/cosmos-compose.json",
|
||||
"supported_architectures": [
|
||||
"amd64",
|
||||
"arm7",
|
||||
"arm64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Uptime Kuma",
|
||||
"description": "Uptime Kuma is an open-source, self-hosted monitoring tool for tracking the uptime of online services. It offers status checks for various services, detailed statistics with charts, and multiple notification methods. Uptime Kuma is compatible with Windows, Linux, MacOS, and Docker.",
|
||||
|
@ -1283,6 +1826,58 @@ function list() {
|
|||
"arm64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Van_DAM",
|
||||
"description": "A self-hosted digital asset manager for 3d print files",
|
||||
"url": "",
|
||||
"longDescription": "<p>VanDAM is a Digital Asset Manager (DAM), specifically designed for 3D print files. Create a library pointing at your files on disk, and it will scan for models and parts. It assumes that any folders containing STL or OBJ files are models, and the files within them are parts. You can then view the files easily through your browser!</p>",
|
||||
"tags": [
|
||||
"3d models",
|
||||
"3d printing"
|
||||
],
|
||||
"repository": "https://github.com/Floppy/van_dam",
|
||||
"image": "ghcr.io/floppy/van_dam",
|
||||
"screenshots": [
|
||||
"https://azukaar.github.io/cosmos-servapps-official/servapps/Van_DAM/screenshots/1.jpg",
|
||||
"https://azukaar.github.io/cosmos-servapps-official/servapps/Van_DAM/screenshots/2.jpg"
|
||||
],
|
||||
"icon": "https://azukaar.github.io/cosmos-servapps-official/servapps/Van_DAM/icon.png",
|
||||
"compose": "https://azukaar.github.io/cosmos-servapps-official/servapps/Van_DAM/cosmos-compose.json",
|
||||
"supported_architectures": [
|
||||
"amd64",
|
||||
"arm64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Vaultwarden",
|
||||
"description": "Vaultwarden is an open-source, self-hosted password management server compatible with Bitwarden apps. Written in Rust, it provides a secure way to store and synchronize sensitive data across devices with lightweight and low resource usage. Vaultwarden supports various platforms, including Linux, Windows, MacOS, and Docker.",
|
||||
"url": "",
|
||||
"longDescription": "<p>Vaultwarden is an open-source password management server that is compatible with Bitwarden apps. It provides a secure and free self-hosted solution to store and synchronize sensitive data across multiple devices, with the ability to share data across organizations, users, and teams.</p><p>Written in Rust, Vaultwarden is designed for lightweight and low resource usage. It allows you to store all kinds of sensitive information, including passwords, credit card details, and secure notes, which can be accessed through Bitwarden's web, mobile, or browser extension apps.</p><p>Vaultwarden supports various platforms, including Linux, Windows, MacOS, and can be deployed easily using Docker.</p>",
|
||||
"tags": [
|
||||
"productivity",
|
||||
"password-manager",
|
||||
"password manager",
|
||||
"bitwarden",
|
||||
"rust",
|
||||
"sensitive data",
|
||||
"synchronization",
|
||||
"linux",
|
||||
"windows",
|
||||
"macos",
|
||||
"docker"
|
||||
],
|
||||
"repository": "https://github.com/dani-garcia/vaultwarden",
|
||||
"image": "https://hub.docker.com/r/vaultwarden/server",
|
||||
"screenshots": [
|
||||
"https://azukaar.github.io/cosmos-servapps-official/servapps/Vaultwarden/screenshots/1.png"
|
||||
],
|
||||
"icon": "https://azukaar.github.io/cosmos-servapps-official/servapps/Vaultwarden/icon.png",
|
||||
"compose": "https://azukaar.github.io/cosmos-servapps-official/servapps/Vaultwarden/cosmos-compose.json",
|
||||
"supported_architectures": [
|
||||
"amd64",
|
||||
"arm64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "WordPress",
|
||||
"description": "WordPress is a free, open-source content management system based on PHP and MySQL. Known for its flexibility and ease-of-use, it supports a range of website types with thousands of plugins and themes. WordPress features a robust administrative dashboard for website creation, modification, and management. It is widely supported across various hosting platforms.",
|
||||
|
@ -1310,12 +1905,36 @@ function list() {
|
|||
"amd64",
|
||||
"arm64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ntfy",
|
||||
"description": "Send push notifications to your phone or desktop using PUT/POST.",
|
||||
"url": "",
|
||||
"longDescription": "<p>ntfy (pronounced \"notify\") is a simple HTTP-based pub-sub notification service. With ntfy, you can send notifications to your phone or desktop via scripts from any computer, without having to sign up or pay any fees.</p>",
|
||||
"tags": [
|
||||
"notifications",
|
||||
"rest api",
|
||||
"curl",
|
||||
"push notifications"
|
||||
],
|
||||
"repository": "https://github.com/binwiederhier/ntfy",
|
||||
"image": "https://hub.docker.com/r/binwiederhier/ntfy",
|
||||
"screenshots": [
|
||||
"https://azukaar.github.io/cosmos-servapps-official/servapps/ntfy/screenshots/1.png",
|
||||
"https://azukaar.github.io/cosmos-servapps-official/servapps/ntfy/screenshots/2.png"
|
||||
],
|
||||
"icon": "https://azukaar.github.io/cosmos-servapps-official/servapps/ntfy/icon.png",
|
||||
"compose": "https://azukaar.github.io/cosmos-servapps-official/servapps/ntfy/cosmos-compose.json",
|
||||
"supported_architectures": [
|
||||
"amd64",
|
||||
"arm64"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"status": "OK"
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
BIN
client/src/assets/images/icons/constellation.png
Normal file
BIN
client/src/assets/images/icons/constellation.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.5 KiB |
94
client/src/components/apiModal.jsx
Normal file
94
client/src/components/apiModal.jsx
Normal file
|
@ -0,0 +1,94 @@
|
|||
// material-ui
|
||||
import { LoadingButton } from '@mui/lab';
|
||||
import { Button } from '@mui/material';
|
||||
import Dialog from '@mui/material/Dialog';
|
||||
import DialogActions from '@mui/material/DialogActions';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
import DialogContentText from '@mui/material/DialogContentText';
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
import * as React from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
const preStyle = {
|
||||
backgroundColor: '#000',
|
||||
color: '#fff',
|
||||
padding: '10px',
|
||||
borderRadius: '5px',
|
||||
overflow: 'auto',
|
||||
maxHeight: '500px',
|
||||
maxWidth: '100%',
|
||||
width: '100%',
|
||||
margin: '0',
|
||||
position: 'relative',
|
||||
fontSize: '12px',
|
||||
fontFamily: 'monospace',
|
||||
whiteSpace: 'pre-wrap',
|
||||
wordWrap: 'break-word',
|
||||
wordBreak: 'break-all',
|
||||
lineHeight: '1.5',
|
||||
boxShadow: '0 0 10px rgba(0,0,0,0.5)',
|
||||
border: '1px solid rgba(255,255,255,0.1)',
|
||||
boxSizing: 'border-box',
|
||||
marginBottom: '10px',
|
||||
marginTop: '10px',
|
||||
marginLeft: '0',
|
||||
marginRight: '0',
|
||||
display: 'block',
|
||||
textAlign: 'left',
|
||||
verticalAlign: 'baseline',
|
||||
opacity: '1',
|
||||
}
|
||||
|
||||
const ApiModal = ({ callback, label }) => {
|
||||
const [openModal, setOpenModal] = useState(false);
|
||||
const [content, setContent] = useState("");
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const getContent = async () => {
|
||||
setLoading(true);
|
||||
let content = await callback();
|
||||
setContent(content.data);
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (openModal)
|
||||
getContent();
|
||||
}, [openModal]);
|
||||
|
||||
return <>
|
||||
<Dialog open={openModal} onClose={() => setOpenModal(false)}>
|
||||
<DialogTitle>Refresh Page</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
<pre style={preStyle}>
|
||||
{content}
|
||||
</pre>
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<LoadingButton
|
||||
loading={loading}
|
||||
onClick={() => {
|
||||
getContent();
|
||||
}}>Refresh</LoadingButton>
|
||||
<Button onClick={() => {
|
||||
setOpenModal(false);
|
||||
}}>Close</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
<Button
|
||||
disableElevation
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
setOpenModal(true);
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</Button>
|
||||
</>
|
||||
};
|
||||
|
||||
export default ApiModal;
|
48
client/src/components/confirmModal.jsx
Normal file
48
client/src/components/confirmModal.jsx
Normal file
|
@ -0,0 +1,48 @@
|
|||
// material-ui
|
||||
import { LoadingButton } from '@mui/lab';
|
||||
import { Button } from '@mui/material';
|
||||
import Dialog from '@mui/material/Dialog';
|
||||
import DialogActions from '@mui/material/DialogActions';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
import DialogContentText from '@mui/material/DialogContentText';
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
import * as React from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
const ConfirmModal = ({ callback, label, content }) => {
|
||||
const [openModal, setOpenModal] = useState(false);
|
||||
|
||||
return <>
|
||||
<Dialog open={openModal} onClose={() => setOpenModal(false)}>
|
||||
<DialogTitle>Are you sure?</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
{content}
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => {
|
||||
setOpenModal(false);
|
||||
}}>Cancel</Button>
|
||||
<LoadingButton
|
||||
onClick={() => {
|
||||
callback();
|
||||
setOpenModal(false);
|
||||
}}>Confirm</LoadingButton>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
<Button
|
||||
disableElevation
|
||||
variant="outlined"
|
||||
color="warning"
|
||||
onClick={() => {
|
||||
setOpenModal(true);
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</Button>
|
||||
</>
|
||||
};
|
||||
|
||||
export default ConfirmModal;
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||
import { Button } from '@mui/material';
|
||||
import { UploadOutlined } from '@ant-design/icons';
|
||||
|
||||
export default function UploadButtons({OnChange, accept, label}) {
|
||||
export default function UploadButtons({OnChange, accept, label, variant, fullWidth, size}) {
|
||||
return (
|
||||
<div>
|
||||
<input
|
||||
|
@ -14,7 +14,8 @@ export default function UploadButtons({OnChange, accept, label}) {
|
|||
onChange={OnChange}
|
||||
/>
|
||||
<label htmlFor="contained-button-file">
|
||||
<Button variant="contained" component="span" startIcon={<UploadOutlined />}>
|
||||
<Button variant={variant || "contained"} component="span"
|
||||
fullWidth={fullWidth} startIcon={<UploadOutlined />}>
|
||||
{label || 'Upload'}
|
||||
</Button>
|
||||
</label>
|
||||
|
|
|
@ -123,8 +123,9 @@
|
|||
align-items: center;
|
||||
}
|
||||
|
||||
.loading-image {
|
||||
background: url('/assets/images/icons/cosmos_gray.png') no-repeat center center;
|
||||
.loading-image:empty {
|
||||
/* background: url('assets/images/icons/cosmos_gray.png') no-repeat center center;
|
||||
background-size: contain; */
|
||||
}
|
||||
|
||||
.raw-table table {
|
||||
|
|
|
@ -9,6 +9,7 @@ import { Avatar, Chip, ListItemButton, ListItemIcon, ListItemText, Typography }
|
|||
|
||||
// project import
|
||||
import { activeItem } from '../../../../../store/reducers/menu';
|
||||
import { useClientInfos } from '../../../../../utils/hooks';
|
||||
|
||||
// ==============================|| NAVIGATION - LIST ITEM ||============================== //
|
||||
|
||||
|
@ -17,6 +18,12 @@ const NavItem = ({ item, level }) => {
|
|||
const dispatch = useDispatch();
|
||||
const menu = useSelector((state) => state.menu);
|
||||
const { drawerOpen, openItem } = menu;
|
||||
const {role} = useClientInfos();
|
||||
const isAdmin = role === "2";
|
||||
|
||||
if (item.adminOnly && !isAdmin) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let itemTarget = '_self';
|
||||
if (item.target) {
|
||||
|
@ -54,6 +61,16 @@ const NavItem = ({ item, level }) => {
|
|||
const textColor = 'text.primary';
|
||||
const iconSelectedColor = 'primary.main';
|
||||
|
||||
// SET BETA (TODO REMOVE)
|
||||
if(item.title === "Constellation")
|
||||
item.title = <>{item.title} <span style={{
|
||||
color: 'gray',
|
||||
fontSize: '11px',
|
||||
textDecoration: 'italic',
|
||||
transform: 'translateY(-5px)',
|
||||
display: 'inline-block',
|
||||
}}>Beta</span></>;
|
||||
|
||||
return (
|
||||
<ListItemButton
|
||||
{...listItemProps}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// assets
|
||||
import { ProfileOutlined, PicLeftOutlined, SettingOutlined, NodeExpandOutlined, AppstoreOutlined} from '@ant-design/icons';
|
||||
import ConstellationIcon from '../assets/images/icons/constellation.png'
|
||||
|
||||
// icons
|
||||
const icons = {
|
||||
|
@ -7,7 +8,6 @@ const icons = {
|
|||
ProfileOutlined,
|
||||
SettingOutlined
|
||||
};
|
||||
|
||||
// ==============================|| MENU ITEMS - EXTRA PAGES ||============================== //
|
||||
|
||||
const pages = {
|
||||
|
@ -20,7 +20,8 @@ const pages = {
|
|||
title: 'ServApps',
|
||||
type: 'item',
|
||||
url: '/cosmos-ui/servapps',
|
||||
icon: AppstoreOutlined
|
||||
icon: AppstoreOutlined,
|
||||
adminOnly: true
|
||||
},
|
||||
{
|
||||
id: 'url',
|
||||
|
@ -29,12 +30,21 @@ const pages = {
|
|||
url: '/cosmos-ui/config-url',
|
||||
icon: icons.NodeExpandOutlined,
|
||||
},
|
||||
{
|
||||
id: 'constellation',
|
||||
title: 'Constellation',
|
||||
type: 'item',
|
||||
url: '/cosmos-ui/constellation',
|
||||
icon: () => <img height="28px" width="28px" style={{marginLeft: "-6px"}} src={ConstellationIcon} />,
|
||||
|
||||
},
|
||||
{
|
||||
id: 'users',
|
||||
title: 'Users',
|
||||
type: 'item',
|
||||
url: '/cosmos-ui/config-users',
|
||||
icon: icons.ProfileOutlined,
|
||||
adminOnly: true
|
||||
},
|
||||
{
|
||||
id: 'openid',
|
||||
|
@ -42,6 +52,7 @@ const pages = {
|
|||
type: 'item',
|
||||
url: '/cosmos-ui/openid-manage',
|
||||
icon: PicLeftOutlined,
|
||||
adminOnly: true
|
||||
},
|
||||
{
|
||||
id: 'config',
|
||||
|
|
|
@ -9,7 +9,7 @@ import AuthWrapper from './AuthWrapper';
|
|||
import { useEffect } from 'react';
|
||||
|
||||
import * as API from '../../api';
|
||||
import { redirectTo } from '../../utils/indexs';
|
||||
import { redirectTo, redirectToLocal } from '../../utils/indexs';
|
||||
|
||||
// ================================|| REGISTER ||================================ //
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ import {
|
|||
FormHelperText,
|
||||
} from '@mui/material';
|
||||
import RestartModal from '../users/restart';
|
||||
import { CosmosCheckbox, CosmosFormDivider, CosmosInputText, CosmosSelect } from '../users/formShortcuts';
|
||||
import { CosmosCheckbox, CosmosCollapse, CosmosFormDivider, CosmosInputText, CosmosSelect } from '../users/formShortcuts';
|
||||
import { CosmosContainerPicker } from '../users/containerPicker';
|
||||
import { snackit } from '../../../api/wrap';
|
||||
import { ValidateRouteSchema, sanitizeRoute } from '../../../utils/routes';
|
||||
|
@ -72,12 +72,20 @@ const RouteManagement = ({ routeConfig, routeNames, config, TargetContainer, noC
|
|||
StripPathPrefix: routeConfig.StripPathPrefix,
|
||||
AuthEnabled: routeConfig.AuthEnabled,
|
||||
_SmartShield_Enabled: (routeConfig.SmartShield ? routeConfig.SmartShield.Enabled : false),
|
||||
RestrictToConstellation: routeConfig.RestrictToConstellation,
|
||||
OverwriteHostHeader: routeConfig.OverwriteHostHeader,
|
||||
WhitelistInboundIPs: routeConfig.WhitelistInboundIPs && routeConfig.WhitelistInboundIPs.join(', '),
|
||||
}}
|
||||
validationSchema={ValidateRouteSchema}
|
||||
onSubmit={async (values, { setErrors, setStatus, setSubmitting }) => {
|
||||
if(!submitButton) {
|
||||
return false;
|
||||
} else {
|
||||
let commaSepIps = values.WhitelistInboundIPs;
|
||||
if(commaSepIps) {
|
||||
values.WhitelistInboundIPs = commaSepIps.split(',').map((ip) => ip.trim());
|
||||
}
|
||||
|
||||
let fullValues = {
|
||||
...routeConfig,
|
||||
...values,
|
||||
|
@ -256,6 +264,37 @@ const RouteManagement = ({ routeConfig, routeNames, config, TargetContainer, noC
|
|||
label="Smart Shield Protection"
|
||||
formik={formik}
|
||||
/>
|
||||
|
||||
<CosmosCheckbox
|
||||
name="RestrictToConstellation"
|
||||
label="Restrict access to Constellation VPN"
|
||||
formik={formik}
|
||||
/>
|
||||
|
||||
<CosmosCollapse title={'Advanced Settings'}>
|
||||
<Stack spacing={2}>
|
||||
<Alert severity='info'>These settings are for advanced users only. Please do not change these unless you know what you are doing.</Alert>
|
||||
<CosmosInputText
|
||||
name="OverwriteHostHeader"
|
||||
label="Overwrite Host Header (use this to chain resolve request from another server/ip)"
|
||||
placeholder="Overwrite Host Header"
|
||||
formik={formik}
|
||||
/>
|
||||
|
||||
<Alert severity='warning'>
|
||||
This setting will filter out all requests that do not come from the specified IPs.
|
||||
This requires your setup to report the true IP of the client. By default it will, but some exotic setup (like installing docker/cosmos on Windows, or behind Cloudlfare)
|
||||
will prevent Cosmos from knowing what is the client's real IP. If you used "Restrict to Constellation" above, Constellation IPs will always be allowed regardless of this setting.
|
||||
</Alert>
|
||||
|
||||
<CosmosInputText
|
||||
name="WhitelistInboundIPs"
|
||||
label="Whitelist Inbound IPs and/or IP ranges (comma separated)"
|
||||
placeholder="Whitelist Inbound IPs"
|
||||
formik={formik}
|
||||
/>
|
||||
</Stack>
|
||||
</CosmosCollapse>
|
||||
</Grid>
|
||||
</MainCard>
|
||||
{submitButton && <MainCard ><Button
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import * as React from 'react';
|
||||
import MainCard from '../../../components/MainCard';
|
||||
import RestartModal from '../users/restart';
|
||||
import { Chip, Divider, Stack, useMediaQuery } from '@mui/material';
|
||||
import { Checkbox, Chip, Divider, FormControlLabel, Stack, useMediaQuery } from '@mui/material';
|
||||
import HostChip from '../../../components/hostChip';
|
||||
import { RouteMode, RouteSecurity } from '../../../components/routeComponents';
|
||||
import { getFaviconURL } from '../../../utils/routes';
|
||||
|
@ -9,6 +9,8 @@ import * as API from '../../../api';
|
|||
import { CheckOutlined, ClockCircleOutlined, DashboardOutlined, DeleteOutlined, DownOutlined, LockOutlined, UpOutlined } from "@ant-design/icons";
|
||||
import IsLoggedIn from '../../../isLoggedIn';
|
||||
import { redirectToLocal } from '../../../utils/indexs';
|
||||
import { CosmosCheckbox } from '../users/formShortcuts';
|
||||
import { Field } from 'formik';
|
||||
|
||||
const info = {
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.1)',
|
||||
|
|
|
@ -31,6 +31,7 @@ import { TwitterPicker
|
|||
|
||||
// TODO: Remove circular deps
|
||||
import {SetPrimaryColor, SetSecondaryColor} from '../../../App';
|
||||
import { useClientInfos } from '../../../utils/hooks';
|
||||
|
||||
const ConfigManagement = () => {
|
||||
const [config, setConfig] = React.useState(null);
|
||||
|
@ -38,6 +39,8 @@ const ConfigManagement = () => {
|
|||
const [openResartModal, setOpenRestartModal] = React.useState(false);
|
||||
const [uploadingBackground, setUploadingBackground] = React.useState(false);
|
||||
const [saveLabel, setSaveLabel] = React.useState("Save");
|
||||
const {role} = useClientInfos();
|
||||
const isAdmin = role === "2";
|
||||
|
||||
function refresh() {
|
||||
API.config.get().then((res) => {
|
||||
|
@ -62,9 +65,9 @@ const ConfigManagement = () => {
|
|||
refresh();
|
||||
}}>Refresh</Button>
|
||||
|
||||
<Button variant="outlined" color="primary" startIcon={<SyncOutlined />} onClick={() => {
|
||||
{isAdmin && <Button variant="outlined" color="primary" startIcon={<SyncOutlined />} onClick={() => {
|
||||
setOpenRestartModal(true);
|
||||
}}>Restart Server</Button>
|
||||
}}>Restart Server</Button>}
|
||||
</Stack>
|
||||
|
||||
{config && <>
|
||||
|
@ -186,7 +189,7 @@ const ConfigManagement = () => {
|
|||
{(formik) => (
|
||||
<form noValidate onSubmit={formik.handleSubmit}>
|
||||
<Stack spacing={3}>
|
||||
<MainCard>
|
||||
{isAdmin && <MainCard>
|
||||
{formik.errors.submit && (
|
||||
<Grid item xs={12}>
|
||||
<FormHelperText error>{formik.errors.submit}</FormHelperText>
|
||||
|
@ -205,7 +208,13 @@ const ConfigManagement = () => {
|
|||
{saveLabel}
|
||||
</LoadingButton>
|
||||
</Grid>
|
||||
</MainCard>
|
||||
</MainCard>}
|
||||
|
||||
{!isAdmin && <div>
|
||||
<Alert severity="warning">As you are not an admin, you can't edit the configuration.
|
||||
This page is only here for visibility.
|
||||
</Alert>
|
||||
</div>}
|
||||
|
||||
<MainCard title="General">
|
||||
<Grid container spacing={3}>
|
||||
|
@ -331,6 +340,29 @@ const ConfigManagement = () => {
|
|||
formik.setFieldValue('PrimaryColor', colorRGB);
|
||||
SetPrimaryColor(colorRGB);
|
||||
}}
|
||||
colors={[
|
||||
'#ab47bc',
|
||||
'#4527a0',
|
||||
'#FF6900',
|
||||
'#FCB900',
|
||||
'#7BDCB5',
|
||||
'#00D084',
|
||||
'#8ED1FC',
|
||||
'#0693E3',
|
||||
'#ABB8C3',
|
||||
'#EB144C',
|
||||
'#F78DA7',
|
||||
'#9900EF',
|
||||
'#FF0000',
|
||||
'#FFC0CB',
|
||||
'#20B2AA',
|
||||
'#FFFF00',
|
||||
'#8A2BE2',
|
||||
'#A52A2A',
|
||||
'#5F9EA0',
|
||||
'#7FFF00',
|
||||
'#D2691E'
|
||||
]}
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
|
@ -346,6 +378,29 @@ const ConfigManagement = () => {
|
|||
formik.setFieldValue('SecondaryColor', colorRGB);
|
||||
SetSecondaryColor(colorRGB);
|
||||
}}
|
||||
colors={[
|
||||
'#ab47bc',
|
||||
'#4527a0',
|
||||
'#FF6900',
|
||||
'#FCB900',
|
||||
'#7BDCB5',
|
||||
'#00D084',
|
||||
'#8ED1FC',
|
||||
'#0693E3',
|
||||
'#ABB8C3',
|
||||
'#EB144C',
|
||||
'#F78DA7',
|
||||
'#9900EF',
|
||||
'#FF0000',
|
||||
'#FFC0CB',
|
||||
'#20B2AA',
|
||||
'#FFFF00',
|
||||
'#8A2BE2',
|
||||
'#A52A2A',
|
||||
'#5F9EA0',
|
||||
'#7FFF00',
|
||||
'#D2691E'
|
||||
]}
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
|
@ -627,7 +682,7 @@ const ConfigManagement = () => {
|
|||
</Grid>
|
||||
</MainCard>
|
||||
|
||||
<MainCard>
|
||||
{isAdmin && <MainCard>
|
||||
{formik.errors.submit && (
|
||||
<Grid item xs={12}>
|
||||
<FormHelperText error>{formik.errors.submit}</FormHelperText>
|
||||
|
@ -646,7 +701,7 @@ const ConfigManagement = () => {
|
|||
{saveLabel}
|
||||
</LoadingButton>
|
||||
</Grid>
|
||||
</MainCard>
|
||||
</MainCard>}
|
||||
</Stack>
|
||||
</form>
|
||||
)}
|
||||
|
|
|
@ -27,26 +27,33 @@ import { strengthColor, strengthIndicator } from '../../../utils/password-streng
|
|||
|
||||
import { EyeOutlined, EyeInvisibleOutlined } from '@ant-design/icons';
|
||||
|
||||
export const CosmosInputText = ({ name, style, multiline, type, placeholder, onChange, label, formik }) => {
|
||||
export const CosmosInputText = ({ name, style, value, errors, multiline, type, placeholder, onChange, label, formik }) => {
|
||||
return <Grid item xs={12}>
|
||||
<Stack spacing={1} style={style}>
|
||||
<InputLabel htmlFor={name}>{label}</InputLabel>
|
||||
{label && <InputLabel htmlFor={name}>{label}</InputLabel>}
|
||||
<OutlinedInput
|
||||
id={name}
|
||||
type={type ? type : 'text'}
|
||||
value={formik.values[name]}
|
||||
value={value || (formik && formik.values[name])}
|
||||
name={name}
|
||||
multiline={multiline}
|
||||
onBlur={formik.handleBlur}
|
||||
onBlur={(...ar) => {
|
||||
return formik && formik.handleBlur(...ar);
|
||||
}}
|
||||
onChange={(...ar) => {
|
||||
onChange && onChange(...ar);
|
||||
return formik.handleChange(...ar);
|
||||
return formik && formik.handleChange(...ar);
|
||||
}}
|
||||
placeholder={placeholder}
|
||||
fullWidth
|
||||
error={Boolean(formik.touched[name] && formik.errors[name])}
|
||||
error={Boolean(formik && formik.touched[name] && formik.errors[name])}
|
||||
/>
|
||||
{formik.touched[name] && formik.errors[name] && (
|
||||
{formik && formik.touched[name] && formik.errors[name] && (
|
||||
<FormHelperText error id="standard-weight-helper-text-name-login">
|
||||
{formik.errors[name]}
|
||||
</FormHelperText>
|
||||
)}
|
||||
{errors && (
|
||||
<FormHelperText error id="standard-weight-helper-text-name-login">
|
||||
{formik.errors[name]}
|
||||
</FormHelperText>
|
||||
|
@ -206,7 +213,7 @@ export const CosmosCollapse = ({ children, title }) => {
|
|||
export function CosmosFormDivider({title}) {
|
||||
return <Grid item xs={12}>
|
||||
<Divider>
|
||||
<Chip label={title} />
|
||||
{title && <Chip label={title} />}
|
||||
</Divider>
|
||||
</Grid>
|
||||
}
|
||||
|
|
302
client/src/pages/constellation/addDevice.jsx
Normal file
302
client/src/pages/constellation/addDevice.jsx
Normal file
|
@ -0,0 +1,302 @@
|
|||
// material-ui
|
||||
import { Alert, Button, InputLabel, OutlinedInput, Stack, TextField } from '@mui/material';
|
||||
import Dialog from '@mui/material/Dialog';
|
||||
import DialogActions from '@mui/material/DialogActions';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
import DialogContentText from '@mui/material/DialogContentText';
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
import * as React from 'react';
|
||||
import { useState } from 'react';
|
||||
import ResponsiveButton from '../../components/responseiveButton';
|
||||
import { PlusCircleFilled } from '@ant-design/icons';
|
||||
import { Formik } from 'formik';
|
||||
import * as yup from 'yup';
|
||||
import * as API from '../../api';
|
||||
import { CosmosCheckbox, CosmosFormDivider, CosmosInputText, CosmosSelect } from '../config/users/formShortcuts';
|
||||
import { DownloadFile } from '../../api/downloadButton';
|
||||
import QRCode from 'qrcode';
|
||||
import { useClientInfos } from '../../utils/hooks';
|
||||
|
||||
const getDocker = (data, isCompose) => {
|
||||
let lighthouses = '';
|
||||
|
||||
for (let i = 0; i < data.LighthousesList.length; i++) {
|
||||
const l = data.LighthousesList[i];
|
||||
lighthouses += l.publicHostname + ";" + l.ip + ":" + l.port + ";" + l.isRelay + ",";
|
||||
}
|
||||
|
||||
let containerName = "cosmos-constellation-lighthouse";
|
||||
let imageName = "cosmos-constellation-lighthouse:latest";
|
||||
|
||||
let volPath = "/var/lib/cosmos-constellation";
|
||||
|
||||
if (isCompose) {
|
||||
return `
|
||||
version: "3.8"
|
||||
services:
|
||||
${containerName}:
|
||||
image: ${imageName}
|
||||
container_name: ${containerName}
|
||||
restart: unless-stopped
|
||||
network_mode: bridge
|
||||
ports:
|
||||
- "${data.Port}:4242"
|
||||
volumes:
|
||||
- ${volPath}:/config
|
||||
environment:
|
||||
- CA=${JSON.stringify(data.CA)}
|
||||
- CERT=${JSON.stringify(data.PrivateKey)}
|
||||
- KEY=${JSON.stringify(data.PublicKey)}
|
||||
- LIGHTHOUSES=${lighthouses}
|
||||
- PUBLIC_HOSTNAME=${data.PublicHostname}
|
||||
- IS_RELAY=${data.IsRelay}
|
||||
- IP=${data.IP}
|
||||
`;
|
||||
} else {
|
||||
return `
|
||||
docker run -d \\
|
||||
--name ${containerName} \\
|
||||
--restart unless-stopped \\
|
||||
--network bridge \\
|
||||
-v ${volPath}:/config \\
|
||||
-e CA=${JSON.stringify(data.CA)} \\
|
||||
-e CERT=${JSON.stringify(data.PrivateKey)} \\
|
||||
-e KEY=${JSON.stringify(data.PublicKey)} \\
|
||||
-e LIGHTHOUSES=${lighthouses} \\
|
||||
-e PUBLIC_HOSTNAME=${data.PublicHostname} \\
|
||||
-e IS_RELAY=${data.IsRelay} \\
|
||||
-e IP=${data.IP} \\
|
||||
-p ${data.Port}:4242 \\
|
||||
${imageName}
|
||||
`;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
const AddDeviceModal = ({ users, config, refreshConfig, devices }) => {
|
||||
const [openModal, setOpenModal] = useState(false);
|
||||
const [isDone, setIsDone] = useState(null);
|
||||
const canvasRef = React.useRef(null);
|
||||
const {role, nickname} = useClientInfos();
|
||||
const isAdmin = role === "2";
|
||||
|
||||
let firstIP = "192.168.201.2/24";
|
||||
if (devices && devices.length > 0) {
|
||||
const isIpFree = (ip) => {
|
||||
return devices.filter((d) => d.ip === ip).length === 0;
|
||||
}
|
||||
let i = 1;
|
||||
let j = 201;
|
||||
while (!isIpFree(firstIP)) {
|
||||
i++;
|
||||
if (i > 254) {
|
||||
i = 0;
|
||||
j++;
|
||||
}
|
||||
firstIP = "192.168." + j + "." + i + "/24";
|
||||
}
|
||||
}
|
||||
|
||||
const renderCanvas = (data) => {
|
||||
if (!canvasRef.current) return setTimeout(() => {
|
||||
renderCanvas(data);
|
||||
}, 500);
|
||||
|
||||
QRCode.toCanvas(canvasRef.current, JSON.stringify(data),
|
||||
{
|
||||
width: 600,
|
||||
color: {
|
||||
dark: "#000",
|
||||
light: '#fff'
|
||||
}
|
||||
}, function (error) {
|
||||
if (error) console.error(error)
|
||||
})
|
||||
}
|
||||
|
||||
return <>
|
||||
<Dialog open={openModal} onClose={() => setOpenModal(false)}>
|
||||
<Formik
|
||||
initialValues={{
|
||||
nickname: nickname,
|
||||
deviceName: '',
|
||||
ip: firstIP,
|
||||
publicKey: '',
|
||||
Port: "4242",
|
||||
PublicHostname: '',
|
||||
IsRelay: true,
|
||||
isLighthouse: false,
|
||||
}}
|
||||
|
||||
validationSchema={yup.object({
|
||||
})}
|
||||
|
||||
onSubmit={(values, { setSubmitting, setStatus, setErrors }) => {
|
||||
if(values.isLighthouse) values.nickname = null;
|
||||
|
||||
return API.constellation.addDevice(values).then(({data}) => {
|
||||
setIsDone(data);
|
||||
refreshConfig();
|
||||
renderCanvas(data.Config);
|
||||
}).catch((err) => {
|
||||
setErrors(err.response.data);
|
||||
});
|
||||
}}
|
||||
>
|
||||
{(formik) => (
|
||||
<form onSubmit={formik.handleSubmit}>
|
||||
<DialogTitle>Add Device</DialogTitle>
|
||||
|
||||
{isDone ? <DialogContent>
|
||||
<DialogContentText>
|
||||
<p>
|
||||
Device added successfully!
|
||||
Download scan the QR Code from the Cosmos app or download the relevant
|
||||
files to your device along side the config and network certificate to
|
||||
connect:
|
||||
</p>
|
||||
|
||||
<Stack spacing={2} direction={"column"}>
|
||||
{/* {isDone.isLighthouse ? <>
|
||||
<CosmosFormDivider title={"Docker"} />
|
||||
<TextField
|
||||
fullWidth
|
||||
multiline
|
||||
value={getDocker(isDone, false)}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
disabled
|
||||
/>
|
||||
<CosmosFormDivider title={"File (Docker-Compose)"} />
|
||||
<DownloadFile
|
||||
filename={`docker-compose.yml`}
|
||||
content={getDocker(isDone, true)}
|
||||
label={"Download docker-compose.yml"}
|
||||
/>
|
||||
</> : <> */}
|
||||
<CosmosFormDivider title={"QR Code"} />
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<canvas style={{borderRadius: '15px'}} ref={canvasRef} />
|
||||
</div>
|
||||
{/* </>} */}
|
||||
|
||||
<CosmosFormDivider title={"File"} />
|
||||
<DownloadFile
|
||||
filename={`constellation.yml`}
|
||||
content={isDone.Config}
|
||||
label={"Download constellation.yml"}
|
||||
/>
|
||||
</Stack>
|
||||
</DialogContentText>
|
||||
</DialogContent> : <DialogContent>
|
||||
<DialogContentText>
|
||||
<p>Add a Device to the constellation using either the Cosmos or Nebula client</p>
|
||||
<div>
|
||||
<Stack spacing={2} style={{}}>
|
||||
<CosmosCheckbox
|
||||
name="isLighthouse"
|
||||
label="Lighthouse"
|
||||
formik={formik}
|
||||
/>
|
||||
{!formik.values.isLighthouse &&
|
||||
(isAdmin ? <CosmosSelect
|
||||
name="nickname"
|
||||
label="Owner"
|
||||
formik={formik}
|
||||
// disabled={!isAdmin}
|
||||
options={
|
||||
users.map((u) => {
|
||||
return [u.nickname, u.nickname]
|
||||
})
|
||||
}
|
||||
/> : <>
|
||||
<InputLabel>Owner</InputLabel>
|
||||
<OutlinedInput
|
||||
fullWidth
|
||||
multiline
|
||||
value={nickname}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
disabled
|
||||
/>
|
||||
</>)}
|
||||
|
||||
<CosmosInputText
|
||||
name="deviceName"
|
||||
label="Device Name"
|
||||
formik={formik}
|
||||
/>
|
||||
|
||||
<CosmosInputText
|
||||
name="ip"
|
||||
label="Constellation IP Address"
|
||||
formik={formik}
|
||||
/>
|
||||
|
||||
{/* <CosmosInputText
|
||||
name="Port"
|
||||
label="VPN Port (default: 4242)"
|
||||
formik={formik}
|
||||
/> */}
|
||||
|
||||
<CosmosInputText
|
||||
multiline
|
||||
name="publicKey"
|
||||
label="Public Key (Optional)"
|
||||
formik={formik}
|
||||
/>
|
||||
{formik.values.isLighthouse && <>
|
||||
<CosmosFormDivider title={"Lighthouse Setup"} />
|
||||
|
||||
<CosmosInputText
|
||||
name="PublicHostname"
|
||||
label="Public Hostname"
|
||||
formik={formik}
|
||||
/>
|
||||
|
||||
<CosmosCheckbox
|
||||
name="IsRelay"
|
||||
label="Can Relay Traffic"
|
||||
formik={formik}
|
||||
/>
|
||||
</>}
|
||||
<div>
|
||||
{formik.errors && formik.errors.length > 0 && <Stack spacing={2} direction={"column"}>
|
||||
<Alert severity="error">{formik.errors.map((err) => {
|
||||
return <div>{err}</div>
|
||||
})}</Alert>
|
||||
</Stack>}
|
||||
</div>
|
||||
</Stack>
|
||||
</div>
|
||||
</DialogContentText>
|
||||
</DialogContent>}
|
||||
|
||||
<DialogActions>
|
||||
<Button onClick={() => setOpenModal(false)}>Close</Button>
|
||||
{!isDone && <Button color="primary" variant="contained" type="submit">Add</Button>}
|
||||
</DialogActions>
|
||||
</form>
|
||||
|
||||
)}
|
||||
</Formik>
|
||||
</Dialog>
|
||||
|
||||
<ResponsiveButton
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
setIsDone(null);
|
||||
setOpenModal(true);
|
||||
}}
|
||||
variant={
|
||||
"contained"
|
||||
}
|
||||
startIcon={<PlusCircleFilled />}
|
||||
>
|
||||
Add Device
|
||||
</ResponsiveButton>
|
||||
</>;
|
||||
};
|
||||
|
||||
export default AddDeviceModal;
|
174
client/src/pages/constellation/dns.jsx
Normal file
174
client/src/pages/constellation/dns.jsx
Normal file
|
@ -0,0 +1,174 @@
|
|||
import React from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import * as API from "../../api";
|
||||
import AddDeviceModal from "./addDevice";
|
||||
import PrettyTableView from "../../components/tableView/prettyTableView";
|
||||
import { DeleteButton } from "../../components/delete";
|
||||
import { CloudOutlined, CloudServerOutlined, CompassOutlined, DesktopOutlined, LaptopOutlined, MobileOutlined, TabletOutlined } from "@ant-design/icons";
|
||||
import IsLoggedIn from "../../isLoggedIn";
|
||||
import { Alert, Button, CircularProgress, InputLabel, Stack } from "@mui/material";
|
||||
import { CosmosCheckbox, CosmosFormDivider, CosmosInputText } from "../config/users/formShortcuts";
|
||||
import MainCard from "../../components/MainCard";
|
||||
import { Formik } from "formik";
|
||||
import { LoadingButton } from "@mui/lab";
|
||||
import ApiModal from "../../components/apiModal";
|
||||
import { isDomain } from "../../utils/indexs";
|
||||
import ConfirmModal from "../../components/confirmModal";
|
||||
import UploadButtons from "../../components/fileUpload";
|
||||
|
||||
export const ConstellationDNS = () => {
|
||||
const [isAdmin, setIsAdmin] = useState(false);
|
||||
const [config, setConfig] = useState(null);
|
||||
|
||||
const refreshConfig = async () => {
|
||||
let configAsync = await API.config.get();
|
||||
setConfig(configAsync.data);
|
||||
setIsAdmin(configAsync.isAdmin);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
refreshConfig();
|
||||
}, []);
|
||||
|
||||
return <>
|
||||
{(config) ? <>
|
||||
<Stack spacing={2} style={{maxWidth: "1000px"}}>
|
||||
<div>
|
||||
<MainCard title={"Constellation Internal DNS"} content={config.constellationIP}>
|
||||
<Stack spacing={2}>
|
||||
|
||||
<Formik
|
||||
initialValues={{
|
||||
Fallback: config.ConstellationConfig.DNSFallback,
|
||||
DNSBlockBlacklist: config.ConstellationConfig.DNSBlockBlacklist,
|
||||
DNSAdditionalBlocklists: config.ConstellationConfig.DNSAdditionalBlocklists || [],
|
||||
CustomDNSEntries: config.ConstellationConfig.CustomDNSEntries || []
|
||||
}}
|
||||
onSubmit={(values) => {
|
||||
let newConfig = { ...config };
|
||||
newConfig.ConstellationConfig.DNSFallback = values.Fallback;
|
||||
newConfig.ConstellationConfig.DNSBlockBlacklist = values.DNSBlockBlacklist;
|
||||
newConfig.ConstellationConfig.DNSAdditionalBlocklists = values.DNSAdditionalBlocklists;
|
||||
newConfig.ConstellationConfig.CustomDNSEntries = values.CustomDNSEntries;
|
||||
|
||||
return API.config.set(newConfig);
|
||||
}}
|
||||
>
|
||||
{(formik) => (
|
||||
<form onSubmit={formik.handleSubmit}>
|
||||
<Stack spacing={2}>
|
||||
<Alert severity="info">This is a DNS that runs inside your Constellation network. It automatically
|
||||
rewrites your domains DNS entries to be local to your network, and also allows you to do things like block ads
|
||||
and trackers on all devices connected to your network. You can also add custom DNS entries to resolve to specific
|
||||
IP addresses. This DNS server is only accessible from inside your network.</Alert>
|
||||
|
||||
<CosmosInputText formik={formik} name="Fallback" label="DNS Fallback" placeholder={'8.8.8.8:53'} />
|
||||
|
||||
<CosmosFormDivider title={"DNS Blocklists"} />
|
||||
|
||||
<CosmosCheckbox formik={formik} name="DNSBlockBlacklist" label="Use Blacklists to block domains" />
|
||||
|
||||
<Alert severity="warning">When changing your DNS records, always use private mode on your browser and allow some times for various caches to expire.</Alert>
|
||||
|
||||
<InputLabel>DNS Blocklist URLs</InputLabel>
|
||||
{formik.values.DNSAdditionalBlocklists && formik.values.DNSAdditionalBlocklists.map((item, index) => (
|
||||
<Stack direction={"row"} spacing={2} key={`DNSAdditionalBlocklists${item}`} width={"100%"}>
|
||||
<DeleteButton onDelete={() => {
|
||||
formik.setFieldValue("DNSAdditionalBlocklists", [...formik.values.DNSAdditionalBlocklists.slice(0, index), ...formik.values.DNSAdditionalBlocklists.slice(index + 1)]);
|
||||
}} />
|
||||
<div style={{flexGrow: 1}}>
|
||||
<CosmosInputText
|
||||
value={item}
|
||||
name={`DNSAdditionalBlocklists${index}`}
|
||||
placeholder={'https://example.com/blocklist.txt'}
|
||||
onChange={(e) => {
|
||||
formik.setFieldValue("DNSAdditionalBlocklists", [...formik.values.DNSAdditionalBlocklists.slice(0, index), e.target.value, ...formik.values.DNSAdditionalBlocklists.slice(index + 1)]);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Stack>
|
||||
))}
|
||||
<Stack direction="row" spacing={2}>
|
||||
<Button variant="outlined" onClick={() => {
|
||||
formik.setFieldValue("DNSAdditionalBlocklists", [...formik.values.DNSAdditionalBlocklists, ""]);
|
||||
}}>Add</Button>
|
||||
<Button variant="outlined" onClick={() => {
|
||||
formik.setFieldValue("DNSAdditionalBlocklists", [
|
||||
"https://s3.amazonaws.com/lists.disconnect.me/simple_tracking.txt",
|
||||
"https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt",
|
||||
"https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts",
|
||||
"https://raw.githubusercontent.com/StevenBlack/hosts/master/alternates/fakenews-only/hosts"
|
||||
]);
|
||||
}}>Reset Default</Button>
|
||||
</Stack>
|
||||
|
||||
<CosmosFormDivider title={"DNS Custom Entries"} />
|
||||
|
||||
<InputLabel>DNS Custom Entries</InputLabel>
|
||||
{formik.values.CustomDNSEntries && formik.values.CustomDNSEntries.map((item, index) => (
|
||||
<Stack direction={"row"} spacing={2} key={`CustomDNSEntries${item}`} width={"100%"}>
|
||||
<DeleteButton onDelete={() => {
|
||||
formik.setFieldValue("CustomDNSEntries", [...formik.values.CustomDNSEntries.slice(0, index), ...formik.values.CustomDNSEntries.slice(index + 1)]);
|
||||
}} />
|
||||
<div style={{flexGrow: 1}}>
|
||||
<CosmosInputText
|
||||
value={item.Key}
|
||||
name={`CustomDNSEntries${index}-key`}
|
||||
placeholder={'domain.com'}
|
||||
onChange={(e) => {
|
||||
const updatedCustomDNSEntries = [...formik.values.CustomDNSEntries];
|
||||
updatedCustomDNSEntries[index].Key = e.target.value;
|
||||
formik.setFieldValue("CustomDNSEntries", updatedCustomDNSEntries);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div style={{flexGrow: 1}}>
|
||||
<CosmosInputText
|
||||
value={item.Value}
|
||||
name={`CustomDNSEntries${index}-value`}
|
||||
placeholder={'1213.123.123.123'}
|
||||
onChange={(e) => {
|
||||
const updatedCustomDNSEntries = [...formik.values.CustomDNSEntries];
|
||||
updatedCustomDNSEntries[index].Value = e.target.value;
|
||||
formik.setFieldValue("CustomDNSEntries", updatedCustomDNSEntries);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Stack>
|
||||
))}
|
||||
<Stack direction="row" spacing={2}>
|
||||
<Button variant="outlined" onClick={() => {
|
||||
formik.setFieldValue("CustomDNSEntries", [...formik.values.CustomDNSEntries, {
|
||||
Key: "",
|
||||
Value: "",
|
||||
Type: "A"
|
||||
}]);
|
||||
}}>Add</Button>
|
||||
<Button variant="outlined" onClick={() => {
|
||||
formik.setFieldValue("CustomDNSEntries", [
|
||||
]);
|
||||
}}>Reset</Button>
|
||||
</Stack>
|
||||
|
||||
<LoadingButton
|
||||
disableElevation
|
||||
loading={formik.isSubmitting}
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
>
|
||||
Save
|
||||
</LoadingButton>
|
||||
</Stack>
|
||||
</form>
|
||||
)}
|
||||
</Formik>
|
||||
</Stack>
|
||||
</MainCard>
|
||||
</div>
|
||||
</Stack>
|
||||
</> : <center>
|
||||
<CircularProgress color="inherit" size={20} />
|
||||
</center>}
|
||||
</>
|
||||
};
|
57
client/src/pages/constellation/index.jsx
Normal file
57
client/src/pages/constellation/index.jsx
Normal file
|
@ -0,0 +1,57 @@
|
|||
import * as React from 'react';
|
||||
import MainCard from '../../components/MainCard';
|
||||
import { Alert, Chip, Divider, Stack, useMediaQuery } from '@mui/material';
|
||||
import HostChip from '../../components/hostChip';
|
||||
import { RouteMode, RouteSecurity } from '../../components/routeComponents';
|
||||
import { getFaviconURL } from '../../utils/routes';
|
||||
import * as API from '../../api';
|
||||
import { CheckOutlined, ClockCircleOutlined, DashboardOutlined, DeleteOutlined, DownOutlined, LockOutlined, UpOutlined } from "@ant-design/icons";
|
||||
import IsLoggedIn from '../../isLoggedIn';
|
||||
import PrettyTabbedView from '../../components/tabbedView/tabbedView';
|
||||
import { useClientInfos } from '../../utils/hooks';
|
||||
|
||||
import { ConstellationVPN } from './vpn';
|
||||
import { ConstellationDNS } from './dns';
|
||||
|
||||
const ConstellationIndex = () => {
|
||||
const {role} = useClientInfos();
|
||||
const isAdmin = role === "2";
|
||||
|
||||
return isAdmin ? <div>
|
||||
<IsLoggedIn />
|
||||
|
||||
<PrettyTabbedView path="/cosmos-ui/constellation/:tab" tabs={[
|
||||
{
|
||||
title: 'VPN',
|
||||
children: <ConstellationVPN />,
|
||||
path: 'vpn'
|
||||
},
|
||||
{
|
||||
title: 'DNS',
|
||||
children: <ConstellationDNS />,
|
||||
path: 'dns'
|
||||
},
|
||||
{
|
||||
title: 'Firewall',
|
||||
children: <div>
|
||||
<Alert severity="info">
|
||||
Coming soon. This feature will allow you to open and close ports individually
|
||||
on each device and decide who can access them.
|
||||
</Alert>
|
||||
</div>,
|
||||
},
|
||||
{
|
||||
title: 'Unsafe Routes',
|
||||
children: <div>
|
||||
<Alert severity="info">
|
||||
Coming soon. This feature will allow you to tunnel your traffic through
|
||||
your devices to things outside of your constellation.
|
||||
</Alert>
|
||||
</div>,
|
||||
}
|
||||
]}/>
|
||||
|
||||
</div> : <ConstellationVPN />;
|
||||
}
|
||||
|
||||
export default ConstellationIndex;
|
219
client/src/pages/constellation/vpn.jsx
Normal file
219
client/src/pages/constellation/vpn.jsx
Normal file
|
@ -0,0 +1,219 @@
|
|||
import React from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import * as API from "../../api";
|
||||
import AddDeviceModal from "./addDevice";
|
||||
import PrettyTableView from "../../components/tableView/prettyTableView";
|
||||
import { DeleteButton } from "../../components/delete";
|
||||
import { CloudOutlined, CloudServerOutlined, CompassOutlined, DesktopOutlined, LaptopOutlined, MobileOutlined, TabletOutlined } from "@ant-design/icons";
|
||||
import IsLoggedIn from "../../isLoggedIn";
|
||||
import { Alert, Button, CircularProgress, Stack } from "@mui/material";
|
||||
import { CosmosCheckbox, CosmosFormDivider, CosmosInputText } from "../config/users/formShortcuts";
|
||||
import MainCard from "../../components/MainCard";
|
||||
import { Formik } from "formik";
|
||||
import { LoadingButton } from "@mui/lab";
|
||||
import ApiModal from "../../components/apiModal";
|
||||
import { isDomain } from "../../utils/indexs";
|
||||
import ConfirmModal from "../../components/confirmModal";
|
||||
import UploadButtons from "../../components/fileUpload";
|
||||
import { useClientInfos } from "../../utils/hooks";
|
||||
|
||||
const getDefaultConstellationHostname = (config) => {
|
||||
// if domain is set, use it
|
||||
if(isDomain(config.HTTPConfig.Hostname)) {
|
||||
return "vpn." + config.HTTPConfig.Hostname;
|
||||
} else {
|
||||
return config.HTTPConfig.Hostname;
|
||||
}
|
||||
}
|
||||
|
||||
export const ConstellationVPN = () => {
|
||||
const [config, setConfig] = useState(null);
|
||||
const [users, setUsers] = useState(null);
|
||||
const [devices, setDevices] = useState(null);
|
||||
const {role} = useClientInfos();
|
||||
const isAdmin = role === "2";
|
||||
|
||||
const refreshConfig = async () => {
|
||||
let configAsync = await API.config.get();
|
||||
setConfig(configAsync.data);
|
||||
setDevices((await API.constellation.list()).data || []);
|
||||
if(isAdmin)
|
||||
setUsers((await API.users.list()).data || []);
|
||||
else
|
||||
setUsers([]);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
refreshConfig();
|
||||
}, []);
|
||||
|
||||
const getIcon = (r) => {
|
||||
if (r.deviceName.toLowerCase().includes("mobile") || r.deviceName.toLowerCase().includes("phone")) {
|
||||
return <MobileOutlined />
|
||||
}
|
||||
else if (r.deviceName.toLowerCase().includes("laptop") || r.deviceName.toLowerCase().includes("computer")) {
|
||||
return <LaptopOutlined />
|
||||
} else if (r.deviceName.toLowerCase().includes("desktop")) {
|
||||
return <DesktopOutlined />
|
||||
} else if (r.deviceName.toLowerCase().includes("tablet")) {
|
||||
return <TabletOutlined />
|
||||
} else if (r.deviceName.toLowerCase().includes("lighthouse") || r.deviceName.toLowerCase().includes("server")) {
|
||||
return <CompassOutlined />
|
||||
} else {
|
||||
return <CloudOutlined />
|
||||
}
|
||||
}
|
||||
|
||||
return <>
|
||||
{(devices && config && users) ? <>
|
||||
<Stack spacing={2} style={{maxWidth: "1000px"}}>
|
||||
<div>
|
||||
<Alert severity="info">
|
||||
Constellation is a VPN that runs inside your Cosmos network. It automatically
|
||||
connects all your devices together, and allows you to access them from anywhere.
|
||||
Please refer to the <a href="https://cosmos-cloud.io/doc/61 Constellation VPN/" target="_blank">documentation</a> for more information.
|
||||
In order to connect, please use the <a href="https://cosmos-cloud.io/clients" target="_blank">Constellation App</a>.
|
||||
</Alert>
|
||||
<MainCard title={"Constellation Setup"} content={config.constellationIP}>
|
||||
<Stack spacing={2}>
|
||||
{config.ConstellationConfig.Enabled && config.ConstellationConfig.SlaveMode && <>
|
||||
<Alert severity="info">
|
||||
You are currently connected to an external constellation network. Use your main Cosmos server to manage your constellation network and devices.
|
||||
</Alert>
|
||||
</>}
|
||||
<Formik
|
||||
initialValues={{
|
||||
Enabled: config.ConstellationConfig.Enabled,
|
||||
PrivateNode: config.ConstellationConfig.PrivateNode,
|
||||
IsRelay: config.ConstellationConfig.NebulaConfig.Relay.AMRelay,
|
||||
ConstellationHostname: (config.ConstellationConfig.ConstellationHostname && config.ConstellationConfig.ConstellationHostname != "") ? config.ConstellationConfig.ConstellationHostname :
|
||||
getDefaultConstellationHostname(config)
|
||||
}}
|
||||
onSubmit={(values) => {
|
||||
let newConfig = { ...config };
|
||||
newConfig.ConstellationConfig.Enabled = values.Enabled;
|
||||
newConfig.ConstellationConfig.PrivateNode = values.PrivateNode;
|
||||
newConfig.ConstellationConfig.NebulaConfig.Relay.AMRelay = values.IsRelay;
|
||||
newConfig.ConstellationConfig.ConstellationHostname = values.ConstellationHostname;
|
||||
setTimeout(() => {
|
||||
refreshConfig();
|
||||
}, 1500);
|
||||
return API.config.set(newConfig);
|
||||
}}
|
||||
>
|
||||
{(formik) => (
|
||||
<form onSubmit={formik.handleSubmit}>
|
||||
<Stack spacing={2}>
|
||||
{formik.values.Enabled && <Stack spacing={2} direction="row">
|
||||
<Button
|
||||
disableElevation
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
onClick={async () => {
|
||||
await API.constellation.restart();
|
||||
}}
|
||||
>
|
||||
Restart VPN Service
|
||||
</Button>
|
||||
<ApiModal callback={API.constellation.getLogs} label={"Show VPN logs"} />
|
||||
<ApiModal callback={API.constellation.getConfig} label={"Show VPN Config"} />
|
||||
<ConfirmModal
|
||||
variant="outlined"
|
||||
color="warning"
|
||||
label={"Reset Network"}
|
||||
content={"This will completely reset the network, and disconnect all the clients. You will need to reconnect them. This cannot be undone."}
|
||||
callback={async () => {
|
||||
await API.constellation.reset();
|
||||
refreshConfig();
|
||||
}}
|
||||
/>
|
||||
</Stack>}
|
||||
<CosmosCheckbox formik={formik} name="Enabled" label="Constellation Enabled" />
|
||||
{config.ConstellationConfig.Enabled && !config.ConstellationConfig.SlaveMode && <>
|
||||
{formik.values.Enabled && <>
|
||||
<CosmosCheckbox formik={formik} name="IsRelay" label="Relay requests via this Node" />
|
||||
<CosmosCheckbox formik={formik} name="PrivateNode" label="This node is Private (no public IP)" />
|
||||
{!formik.values.PrivateNode && <>
|
||||
<Alert severity="info">This is your Constellation hostname, that you will use to connect. If you are using a domain name, this needs to be different from your server's hostname. Whatever the domain you choose, it is very important that you make sure there is a A entry in your domain DNS pointing to this server. <strong>If you change this value, you will need to reset your network and reconnect all the clients!</strong></Alert>
|
||||
<CosmosInputText formik={formik} name="ConstellationHostname" label="Constellation Hostname" />
|
||||
</>}
|
||||
</>}
|
||||
</>}
|
||||
<LoadingButton
|
||||
disableElevation
|
||||
loading={formik.isSubmitting}
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
>
|
||||
Save
|
||||
</LoadingButton>
|
||||
<UploadButtons
|
||||
accept=".yml,.yaml"
|
||||
label={"Upload External Constellation Network File"}
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
OnChange={async (e) => {
|
||||
let file = e.target.files[0];
|
||||
await API.constellation.connect(file);
|
||||
setTimeout(() => {
|
||||
refreshConfig();
|
||||
}, 1000);
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
</form>
|
||||
)}
|
||||
</Formik>
|
||||
</Stack>
|
||||
</MainCard>
|
||||
</div>
|
||||
{config.ConstellationConfig.Enabled && !config.ConstellationConfig.SlaveMode && <>
|
||||
<CosmosFormDivider title={"Devices"} />
|
||||
<PrettyTableView
|
||||
data={devices.filter((d) => !d.blocked)}
|
||||
getKey={(r) => r.deviceName}
|
||||
buttons={[
|
||||
<AddDeviceModal users={users} config={config} refreshConfig={refreshConfig} devices={devices}/>,
|
||||
]}
|
||||
columns={[
|
||||
{
|
||||
title: '',
|
||||
field: getIcon,
|
||||
},
|
||||
{
|
||||
title: 'Device Name',
|
||||
field: (r) => <strong>{r.deviceName}</strong>,
|
||||
},
|
||||
{
|
||||
title: 'Owner',
|
||||
field: (r) => <strong>{r.nickname}</strong>,
|
||||
},
|
||||
{
|
||||
title: 'Type',
|
||||
field: (r) => <strong>{r.isLighthouse ? "Lighthouse" : "Client"}</strong>,
|
||||
},
|
||||
{
|
||||
title: 'Constellation IP',
|
||||
screenMin: 'md',
|
||||
field: (r) => r.ip,
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
clickable: true,
|
||||
field: (r) => {
|
||||
return <DeleteButton onDelete={async () => {
|
||||
await API.constellation.block(r.nickname, r.deviceName, true);
|
||||
refreshConfig();
|
||||
}}></DeleteButton>
|
||||
}
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</>}
|
||||
</Stack>
|
||||
</> : <center>
|
||||
<CircularProgress color="inherit" size={20} />
|
||||
</center>}
|
||||
</>
|
||||
};
|
|
@ -12,6 +12,7 @@ import { getFullOrigin } from "../../utils/routes";
|
|||
import IsLoggedIn from "../../isLoggedIn";
|
||||
import { ServAppIcon } from "../../utils/servapp-icon";
|
||||
import Chart from 'react-apexcharts';
|
||||
import { useClientInfos } from "../../utils/hooks";
|
||||
|
||||
|
||||
export const HomeBackground = () => {
|
||||
|
@ -87,6 +88,8 @@ const HomePage = () => {
|
|||
const theme = useTheme();
|
||||
const isDark = theme.palette.mode === 'dark';
|
||||
const isMd = useMediaQuery(theme.breakpoints.up('md'));
|
||||
const {role} = useClientInfos();
|
||||
const isAdmin = role === "2";
|
||||
|
||||
const blockStyle = {
|
||||
margin: 0,
|
||||
|
@ -112,9 +115,13 @@ const HomePage = () => {
|
|||
}
|
||||
|
||||
const refreshConfig = () => {
|
||||
if(isAdmin) {
|
||||
API.docker.list().then((res) => {
|
||||
setServApps(res.data);
|
||||
});
|
||||
} else {
|
||||
setServApps([]);
|
||||
}
|
||||
API.config.get().then((res) => {
|
||||
setConfig(res.data);
|
||||
});
|
||||
|
@ -213,47 +220,47 @@ const HomePage = () => {
|
|||
<HomeBackground status={coStatus} />
|
||||
<TransparentHeader />
|
||||
<Stack style={{ zIndex: 2 }} spacing={1}>
|
||||
{coStatus && !coStatus.database && (
|
||||
{isAdmin && coStatus && !coStatus.database && (
|
||||
<Alert severity="error">
|
||||
No Database is setup for Cosmos! User Management and Authentication will not work.<br />
|
||||
You can either setup the database, or disable user management in the configuration panel.<br />
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{coStatus && coStatus.letsencrypt && (
|
||||
{isAdmin && coStatus && coStatus.letsencrypt && (
|
||||
<Alert severity="error">
|
||||
You have enabled Let's Encrypt for automatic HTTPS Certificate. You need to provide the configuration with an email address to use for Let's Encrypt in the configs.
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{coStatus && coStatus.LetsEncryptErrors && coStatus.LetsEncryptErrors.length > 0 && (
|
||||
{isAdmin && coStatus && coStatus.LetsEncryptErrors && coStatus.LetsEncryptErrors.length > 0 && (
|
||||
<Alert severity="error">
|
||||
There are errors with your Let's Encrypt configuration or one of your routes, please fix them as soon as possible.:
|
||||
There are errors with your Let's Encrypt configuration or one of your routes, please fix them as soon as possible:
|
||||
{coStatus.LetsEncryptErrors.map((err) => {
|
||||
return <div> - {err}</div>
|
||||
})}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{coStatus && coStatus.newVersionAvailable && (
|
||||
{isAdmin && coStatus && coStatus.newVersionAvailable && (
|
||||
<Alert severity="warning">
|
||||
A new version of Cosmos is available! Please update to the latest version to get the latest features and bug fixes.
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{coStatus && coStatus.needsRestart && (
|
||||
{isAdmin && coStatus && coStatus.needsRestart && (
|
||||
<Alert severity="warning">
|
||||
You have made changes to the configuration that require a restart to take effect. Please restart Cosmos to apply the changes.
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{coStatus && coStatus.domain && (
|
||||
{isAdmin && coStatus && coStatus.domain && (
|
||||
<Alert severity="error">
|
||||
You are using localhost or 0.0.0.0 as a hostname in the configuration. It is recommended that you use a domain name or an IP instead.
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{coStatus && !coStatus.docker && (
|
||||
{isAdmin && coStatus && !coStatus.docker && (
|
||||
<Alert severity="error">
|
||||
Docker is not connected! Please check your docker connection.<br />
|
||||
Did you forget to add <pre>-v /var/run/docker.sock:/var/run/docker.sock</pre> to your docker run command?<br />
|
||||
|
|
|
@ -12,6 +12,7 @@ import { Link as LinkMUI } from '@mui/material'
|
|||
import DockerComposeImport from '../servapps/containers/docker-compose';
|
||||
import { AppstoreAddOutlined, SearchOutlined } from "@ant-design/icons";
|
||||
import ResponsiveButton from "../../components/responseiveButton";
|
||||
import { useClientInfos } from "../../utils/hooks";
|
||||
|
||||
function Screenshots({ screenshots }) {
|
||||
return screenshots.length > 1 ? (
|
||||
|
@ -23,17 +24,17 @@ function Screenshots({ screenshots }) {
|
|||
: <img src={screenshots[0]} style={{ maxHeight: '300px', height: '100%', maxWidth: '100%' }} />
|
||||
}
|
||||
|
||||
function Showcases({ showcase, isDark }) {
|
||||
function Showcases({ showcase, isDark, isAdmin }) {
|
||||
return (
|
||||
<Carousel animation="slide" navButtonsAlwaysVisible={false} fullHeightHover="true" swipe={false}>
|
||||
{
|
||||
showcase.map((item, i) => <ShowcasesItem isDark={isDark} key={i} item={item} />)
|
||||
showcase.map((item, i) => <ShowcasesItem isDark={isDark} key={i} item={item} isAdmin={isAdmin} />)
|
||||
}
|
||||
</Carousel>
|
||||
)
|
||||
}
|
||||
|
||||
function ShowcasesItem({ isDark, item }) {
|
||||
function ShowcasesItem({ isDark, item, isAdmin }) {
|
||||
return (
|
||||
<Paper style={{
|
||||
position: 'relative',
|
||||
|
@ -68,9 +69,9 @@ function ShowcasesItem({ isDark, item }) {
|
|||
overflow: 'hidden',
|
||||
}}></p>
|
||||
<Stack direction="row" spacing={2} justifyContent="flex-start">
|
||||
<div>
|
||||
{isAdmin && <div>
|
||||
<DockerComposeImport installerInit defaultName={item.name} dockerComposeInit={item.compose} />
|
||||
</div>
|
||||
</div>}
|
||||
<Link to={"/cosmos-ui/market-listing/cosmos-cloud/" + item.name} style={{
|
||||
textDecoration: 'none',
|
||||
}}>
|
||||
|
@ -110,6 +111,8 @@ const MarketPage = () => {
|
|||
const isDark = theme.palette.mode === 'dark';
|
||||
const { appName, appStore } = useParams();
|
||||
const [search, setSearch] = useState("");
|
||||
const {role} = useClientInfos();
|
||||
const isAdmin = role === "2";
|
||||
|
||||
const backgroundStyle = isDark ? {
|
||||
backgroundColor: 'rgb(0,0,0)',
|
||||
|
@ -178,7 +181,7 @@ const MarketPage = () => {
|
|||
</Link>
|
||||
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<Screenshots screenshots={openedApp.screenshots} />
|
||||
<Screenshots screenshots={openedApp.screenshots} isAdmin={isAdmin}/>
|
||||
</div>
|
||||
|
||||
<Stack direction="row" spacing={2}>
|
||||
|
@ -202,9 +205,9 @@ const MarketPage = () => {
|
|||
|
||||
<div dangerouslySetInnerHTML={{ __html: openedApp.longDescription }}></div>
|
||||
|
||||
<div>
|
||||
{isAdmin && <div>
|
||||
<DockerComposeImport installerInit defaultName={openedApp.name} dockerComposeInit={openedApp.compose} />
|
||||
</div>
|
||||
</div>}
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Box>}
|
||||
|
@ -223,7 +226,7 @@ const MarketPage = () => {
|
|||
size={100}
|
||||
/>
|
||||
</Box>}
|
||||
{showcase && showcase.length > 0 && <Showcases showcase={showcase} isDark={isDark} />}
|
||||
{showcase && showcase.length > 0 && <Showcases showcase={showcase} isDark={isDark} isAdmin={isAdmin} />}
|
||||
</Stack>
|
||||
|
||||
<Stack spacing={1} style={{
|
||||
|
|
|
@ -15,6 +15,7 @@ import ContainerIndex from '../pages/servapps/containers';
|
|||
import NewDockerServiceForm from '../pages/servapps/containers/newServiceForm';
|
||||
import OpenIdList from '../pages/openid/openid-list';
|
||||
import MarketPage from '../pages/market/listing';
|
||||
import ConstellationIndex from '../pages/constellation';
|
||||
|
||||
|
||||
// render - dashboard
|
||||
|
@ -44,6 +45,10 @@ const MainRoutes = {
|
|||
path: '/cosmos-ui/dashboard',
|
||||
element: <DashboardDefault />
|
||||
},
|
||||
{
|
||||
path: '/cosmos-ui/constellation',
|
||||
element: <ConstellationIndex />
|
||||
},
|
||||
{
|
||||
path: '/cosmos-ui/servapps',
|
||||
element: <ServAppsIndex />
|
||||
|
|
29
client/src/utils/hooks.js
Normal file
29
client/src/utils/hooks.js
Normal file
|
@ -0,0 +1,29 @@
|
|||
import React from 'react';
|
||||
import { useCookies } from 'react-cookie';
|
||||
import { logout } from '../api/authentication';
|
||||
|
||||
function useClientInfos() {
|
||||
const [cookies] = useCookies(['client-infos']);
|
||||
|
||||
let clientInfos = null;
|
||||
|
||||
try {
|
||||
// Try to parse the cookie into a JavaScript object
|
||||
clientInfos = cookies['client-infos'].split(',');
|
||||
|
||||
return {
|
||||
nickname: clientInfos[0],
|
||||
role: clientInfos[1]
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error parsing client-infos cookie:', error);
|
||||
return {
|
||||
nickname: "",
|
||||
role: 2
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
useClientInfos
|
||||
};
|
|
@ -1,3 +1,5 @@
|
|||
import { Button } from "@mui/material";
|
||||
|
||||
export const randomString = (length) => {
|
||||
let text = "";
|
||||
const possible =
|
||||
|
|
|
@ -29,7 +29,7 @@ WORKDIR /app
|
|||
COPY build/cosmos build/cosmos-arm64 ./
|
||||
|
||||
# Copy other resources
|
||||
COPY build/cosmos_gray.png build/Logo.png build/GeoLite2-Country.mmdb build/meta.json ./
|
||||
COPY build/* ./
|
||||
COPY static ./static
|
||||
|
||||
# Run the respective binary based on the BINARY_NAME
|
||||
|
|
|
@ -13,7 +13,8 @@ RUN apt-get update \
|
|||
|
||||
WORKDIR /app
|
||||
|
||||
COPY build/cosmos build/cosmos_gray.png build/Logo.png build/GeoLite2-Country.mmdb build/meta.json ./
|
||||
|
||||
COPY build/* ./
|
||||
COPY static ./static
|
||||
|
||||
CMD ["./cosmos"]
|
||||
|
|
27
go.mod
27
go.mod
|
@ -8,7 +8,7 @@ require (
|
|||
github.com/docker/docker v23.0.3+incompatible
|
||||
github.com/docker/go-connections v0.4.0
|
||||
github.com/foomo/tlsconfig v0.0.0-20180418120404-b67861b076c9
|
||||
github.com/go-acme/lego/v4 v4.13.3
|
||||
github.com/go-acme/lego/v4 v4.14.2
|
||||
github.com/go-chi/chi v4.0.2+incompatible
|
||||
github.com/go-chi/httprate v0.7.1
|
||||
github.com/go-playground/validator/v10 v10.14.0
|
||||
|
@ -16,7 +16,9 @@ require (
|
|||
github.com/gorilla/mux v1.8.0
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/jasonlvhit/gocron v0.0.1
|
||||
github.com/miekg/dns v1.1.55
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f
|
||||
github.com/natefinch/lumberjack v2.0.0+incompatible
|
||||
github.com/ory/fosite v0.44.0
|
||||
github.com/oschwald/geoip2-golang v1.8.0
|
||||
github.com/pquerna/otp v1.4.0
|
||||
|
@ -27,6 +29,7 @@ require (
|
|||
golang.org/x/crypto v0.10.0
|
||||
golang.org/x/net v0.11.0
|
||||
golang.org/x/sys v0.9.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
||||
require (
|
||||
|
@ -57,7 +60,20 @@ require (
|
|||
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect
|
||||
github.com/andybalholm/cascadia v1.1.0 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 // indirect
|
||||
github.com/aws/aws-sdk-go v1.39.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.19.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.18.28 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.27 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.35 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.29 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.36 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.29 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/lightsail v1.27.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.28.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.12.13 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.13 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.19.3 // indirect
|
||||
github.com/aws/smithy-go v1.13.5 // indirect
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
|
@ -126,7 +142,6 @@ require (
|
|||
github.com/magiconair/properties v1.8.4 // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/mattn/goveralls v0.0.6 // indirect
|
||||
github.com/miekg/dns v1.1.55 // indirect
|
||||
github.com/mimuret/golang-iij-dpf v0.9.1 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
|
@ -137,6 +152,7 @@ require (
|
|||
github.com/montanaflynn/stats v0.7.0 // indirect
|
||||
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 // indirect
|
||||
github.com/nrdcg/auroradns v1.1.0 // indirect
|
||||
github.com/nrdcg/bunny-go v0.0.0-20230728143221-c9dda82568d9 // indirect
|
||||
github.com/nrdcg/desec v0.7.0 // indirect
|
||||
github.com/nrdcg/dnspod-go v0.4.0 // indirect
|
||||
github.com/nrdcg/freemyip v0.2.0 // indirect
|
||||
|
@ -153,7 +169,7 @@ require (
|
|||
github.com/ory/viper v1.7.5 // indirect
|
||||
github.com/ory/x v0.0.214 // indirect
|
||||
github.com/oschwald/maxminddb-golang v1.10.0 // indirect
|
||||
github.com/ovh/go-ovh v1.4.1 // indirect
|
||||
github.com/ovh/go-ovh v1.4.2 // indirect
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
||||
github.com/pborman/uuid v1.2.0 // indirect
|
||||
github.com/pelletier/go-toml v1.8.1 // indirect
|
||||
|
@ -167,7 +183,6 @@ require (
|
|||
github.com/sacloud/packages-go v0.0.9 // indirect
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.17 // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||
github.com/simplesurance/bunny-go v0.0.0-20221115111006-e11d9dc91f04 // indirect
|
||||
github.com/sirupsen/logrus v1.8.1 // indirect
|
||||
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect
|
||||
github.com/softlayer/softlayer-go v1.1.2 // indirect
|
||||
|
@ -210,9 +225,9 @@ require (
|
|||
google.golang.org/grpc v1.53.0 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||
gopkg.in/ns1/ns1-go.v2 v2.7.6 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gotest.tools/v3 v3.4.0 // indirect
|
||||
)
|
||||
|
|
48
go.sum
48
go.sum
|
@ -60,6 +60,7 @@ github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBp
|
|||
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 h1:OBhqkivkhkMqLPymWEppkm7vgPQY2XsHoEkaMQ0AdZY=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||
github.com/DataDog/datadog-go v4.0.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||
|
@ -107,9 +108,35 @@ github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 h1:4daAzAu0
|
|||
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
|
||||
github.com/aws/aws-sdk-go v1.23.19/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48=
|
||||
github.com/aws/aws-sdk-go v1.39.0 h1:74BBwkEmiqBbi2CGflEh34l0YNtIibTjZsibGarkNjo=
|
||||
github.com/aws/aws-sdk-go v1.39.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
||||
github.com/aws/aws-sdk-go-v2 v1.19.0 h1:klAT+y3pGFBU/qVf1uzwttpBbiuozJYWzNLHioyDJ+k=
|
||||
github.com/aws/aws-sdk-go-v2 v1.19.0/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.18.28 h1:TINEaKyh1Td64tqFvn09iYpKiWjmHYrG1fa91q2gnqw=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.18.28/go.mod h1:nIL+4/8JdAuNHEjn/gPEXqtnS02Q3NXB/9Z7o5xE4+A=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.27 h1:dz0yr/yR1jweAnsCx+BmjerUILVPQ6FS5AwF/OyG1kA=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.13.27/go.mod h1:syOqAek45ZXZp29HlnRS/BNgMIW6uiRmeuQsz4Qh2UE=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.5 h1:kP3Me6Fy3vdi+9uHd7YLr6ewPxRL+PU6y15urfTaamU=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.5/go.mod h1:Gj7tm95r+QsDoN2Fhuz/3npQvcZbkEf5mL70n3Xfluc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.35 h1:hMUCiE3Zi5AHrRNGf5j985u0WyqI6r2NULhUfo0N/No=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.35/go.mod h1:ipR5PvpSPqIqL5Mi82BxLnfMkHVbmco8kUwO2xrCi0M=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.29 h1:yOpYx+FTBdpk/g+sBU6Cb1H0U/TLEcYYp66mYqsPpcc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.29/go.mod h1:M/eUABlDbw2uVrdAn+UsI6M727qp2fxkp8K0ejcBDUY=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.36 h1:8r5m1BoAWkn0TDC34lUculryf7nUF25EgIMdjvGCkgo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.36/go.mod h1:Rmw2M1hMVTwiUhjwMoIBFWFJMhvJbct06sSidxInkhY=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.29 h1:IiDolu/eLmuB18DRZibj77n1hHQT7z12jnGO7Ze3pLc=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.29/go.mod h1:fDbkK4o7fpPXWn8YAPmTieAMuB9mk/VgvW64uaUqxd4=
|
||||
github.com/aws/aws-sdk-go-v2/service/lightsail v1.27.2 h1:PwNeYoonBzmTdCztKiiutws3U24KrnDBuabzRfIlZY4=
|
||||
github.com/aws/aws-sdk-go-v2/service/lightsail v1.27.2/go.mod h1:gQhLZrTEath4zik5ixIe6axvgY5jJrgSBDJ360Fxnco=
|
||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.28.4 h1:p4mTxJfCAyiTT4Wp6p/mOPa6j5MqCSRGot8qZwFs+Z0=
|
||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.28.4/go.mod h1:VBLWpaHvhQNeu7N9rMEf00SWeOONb/HvaDUxe/7b44k=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.12.13 h1:sWDv7cMITPcZ21QdreULwxOOAmE05JjEsT6fCDtDA9k=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.12.13/go.mod h1:DfX0sWuT46KpcqbMhJ9QWtxAIP1VozkDWf8VAkByjYY=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.13 h1:BFubHS/xN5bjl818QaroN6mQdjneYQ+AOx44KNXlyH4=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.13/go.mod h1:BzqsVVFduubEmzrVtUFQQIQdFqvUItF8XUq2EnS8Wog=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.19.3 h1:e5mnydVdCVWxP+5rPAGi2PYxC7u2OZgH1ypC114H04U=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.19.3/go.mod h1:yVGZA1CPkmUhBdA039jXNJJG7/6t+G+EBWmFq23xqnY=
|
||||
github.com/aws/aws-xray-sdk-go v0.9.4/go.mod h1:XtMKdBQfpVut+tJEwI7+dJFRxxRdxHDyVNp2tHXRq04=
|
||||
github.com/aws/smithy-go v1.13.5 h1:hgz0X/DX0dGqTYpGALqXJoRKRj5oQ7150i5FdTePzO8=
|
||||
github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
|
@ -259,8 +286,8 @@ github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm
|
|||
github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
|
||||
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||
github.com/go-acme/lego/v4 v4.13.3 h1:aZ1S9FXIkCWG3Uw/rZKSD+MOuO8ZB1t6p9VCg6jJiNY=
|
||||
github.com/go-acme/lego/v4 v4.13.3/go.mod h1:c/iodVGMeBXG/+KiQczoNkySo3YLWTVa0kiyeVd/FHc=
|
||||
github.com/go-acme/lego/v4 v4.14.2 h1:/D/jqRgLi8Cbk33sLGtu2pX2jEg3bGJWHyV8kFuUHGM=
|
||||
github.com/go-acme/lego/v4 v4.14.2/go.mod h1:kBXxbeTg0x9AgaOYjPSwIeJy3Y33zTz+tMD16O4MO6c=
|
||||
github.com/go-bindata/go-bindata v3.1.1+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo=
|
||||
github.com/go-chi/chi v4.0.2+incompatible h1:maB6vn6FqCxrpz4FqWdh4+lwpyZIQS7YEAUcHlgXVRs=
|
||||
github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||
|
@ -672,6 +699,7 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-github/v32 v32.1.0/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI=
|
||||
|
@ -1024,11 +1052,15 @@ github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J
|
|||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
|
||||
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 h1:o6uBwrhM5C8Ll3MAAxrQxRHEu7FkapwTuI2WmL1rw4g=
|
||||
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04/go.mod h1:5sN+Lt1CaY4wsPvgQH/jsuJi4XO2ssZbdsIizr4CVC8=
|
||||
github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4UpgHp07nNdFX7mqFfM=
|
||||
github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk=
|
||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
|
||||
github.com/nicksnyder/go-i18n v1.10.0/go.mod h1:HrK7VCrbOvQoUAQ7Vpy7i87N7JZZZ7R2xBGjv0j365Q=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/nrdcg/auroradns v1.1.0 h1:KekGh8kmf2MNwqZVVYo/fw/ZONt8QMEmbMFOeljteWo=
|
||||
github.com/nrdcg/auroradns v1.1.0/go.mod h1:O7tViUZbAcnykVnrGkXzIJTHoQCHcgalgAe6X1mzHfk=
|
||||
github.com/nrdcg/bunny-go v0.0.0-20230728143221-c9dda82568d9 h1:qpB3wZR4+MPK92cTC9zZPnndkJgDgPvQqPUAgVc1NXU=
|
||||
github.com/nrdcg/bunny-go v0.0.0-20230728143221-c9dda82568d9/go.mod h1:HUoHXDrFvidN1NK9Wb/mZKNOfDNutKkzF2Pg71M9hHA=
|
||||
github.com/nrdcg/desec v0.7.0 h1:iuGhi4pstF3+vJWwt292Oqe2+AsSPKDynQna/eu1fDs=
|
||||
github.com/nrdcg/desec v0.7.0/go.mod h1:e1uRqqKv1mJdd5+SQROAhmy75lKMphLzWIuASLkpeFY=
|
||||
github.com/nrdcg/dnspod-go v0.4.0 h1:c/jn1mLZNKF3/osJ6mz3QPxTudvPArXTjpkmYj0uK6U=
|
||||
|
@ -1124,8 +1156,8 @@ github.com/oschwald/geoip2-golang v1.8.0 h1:KfjYB8ojCEn/QLqsDU0AzrJ3R5Qa9vFlx3z6
|
|||
github.com/oschwald/geoip2-golang v1.8.0/go.mod h1:R7bRvYjOeaoenAp9sKRS8GX5bJWcZ0laWO5+DauEktw=
|
||||
github.com/oschwald/maxminddb-golang v1.10.0 h1:Xp1u0ZhqkSuopaKmk1WwHtjF0H9Hd9181uj2MQ5Vndg=
|
||||
github.com/oschwald/maxminddb-golang v1.10.0/go.mod h1:Y2ELenReaLAZ0b400URyGwvYxHV1dLIxBuyOsyYjHK0=
|
||||
github.com/ovh/go-ovh v1.4.1 h1:VBGa5wMyQtTP7Zb+w97zRCh9sLtM/2YKRyy+MEJmWaM=
|
||||
github.com/ovh/go-ovh v1.4.1/go.mod h1:6bL6pPyUT7tBfI0pqOegJgRjgjuO+mOo+MyXd1EEC0M=
|
||||
github.com/ovh/go-ovh v1.4.2 h1:ub4jVK6ERbiBTo4y5wbLCjeKCjGY+K36e7BviW+MaAU=
|
||||
github.com/ovh/go-ovh v1.4.2/go.mod h1:AkPXVtgwB6xlKblMjRKJJmjRp+ogrE7fz2lVgcQY8SY=
|
||||
github.com/parnurzeal/gorequest v0.2.15/go.mod h1:3Kh2QUMJoqw3icWAecsyzkpY7UzRfDhbRdTjtNwNiUE=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||
|
@ -1241,8 +1273,6 @@ github.com/shurcooL/highlight_go v0.0.0-20170515013102-78fb10f4a5f8/go.mod h1:UD
|
|||
github.com/shurcooL/octicon v0.0.0-20180602230221-c42b0e3b24d9/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
|
||||
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/simplesurance/bunny-go v0.0.0-20221115111006-e11d9dc91f04 h1:ZTzdx88+AcnjqUfJwnz89UBrMSBQ1NEysg9u5d+dU9c=
|
||||
github.com/simplesurance/bunny-go v0.0.0-20221115111006-e11d9dc91f04/go.mod h1:5KS21fpch8TIMyAUv/qQqTa3GZfBDYgjaZbd2KXKYfg=
|
||||
github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
|
||||
github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
|
||||
github.com/sirupsen/logrus v1.1.0/go.mod h1:zrgwTnHtNr00buQ1vSptGe8m1f/BbgsPukg8qsT7A+A=
|
||||
|
@ -1877,6 +1907,8 @@ gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
|||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/mail.v2 v2.0.0-20180731213649-a0242b2233b4/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||
gopkg.in/ns1/ns1-go.v2 v2.7.6 h1:mCPl7q0jbIGACXvGBljAuuApmKZo3rRi4tlRIEbMvjA=
|
||||
gopkg.in/ns1/ns1-go.v2 v2.7.6/go.mod h1:GMnKY+ZuoJ+lVLL+78uSTjwTz2jMazq6AfGKQOYhsPk=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
|
|
32
package-lock.json
generated
32
package-lock.json
generated
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "cosmos-server",
|
||||
"version": "0.8.3",
|
||||
"version": "0.10.0-unstable16",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "cosmos-server",
|
||||
"version": "0.8.3",
|
||||
"version": "0.10.0-unstable16",
|
||||
"dependencies": {
|
||||
"@ant-design/colors": "^6.0.0",
|
||||
"@ant-design/icons": "^4.7.0",
|
||||
|
@ -36,6 +36,7 @@
|
|||
"react": "^18.2.0",
|
||||
"react-apexcharts": "^1.4.0",
|
||||
"react-color": "^2.19.3",
|
||||
"react-cookie": "^6.1.1",
|
||||
"react-copy-to-clipboard": "^5.1.0",
|
||||
"react-device-detect": "^2.2.2",
|
||||
"react-dom": "^18.2.0",
|
||||
|
@ -3708,6 +3709,11 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.1.tgz",
|
||||
"integrity": "sha512-XTIieEY+gvJ39ChLcB4If5zHtPxt3Syj5rgZR+e1ctpmK8NjPf0zFqsz4JpLJT0xla9GFDKjy8Cpu331nrmE1Q=="
|
||||
},
|
||||
"node_modules/@types/cookie": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.5.2.tgz",
|
||||
"integrity": "sha512-DBpRoJGKJZn7RY92dPrgoMew8xCWc2P71beqsjyhEI/Ds9mOyVmBwtekyfhpwFIVt1WrxTonFifiOZ62V8CnNA=="
|
||||
},
|
||||
"node_modules/@types/hast": {
|
||||
"version": "2.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.4.tgz",
|
||||
|
@ -8900,6 +8906,19 @@
|
|||
"react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-cookie": {
|
||||
"version": "6.1.1",
|
||||
"resolved": "https://registry.npmjs.org/react-cookie/-/react-cookie-6.1.1.tgz",
|
||||
"integrity": "sha512-fuFRpf8LH6SfmVMowDUIRywJF5jAUDUWrm0EI5VdXfTl5bPcJ7B0zWbuYpT0Tvikx7Gs18MlvAT+P+744dUz2g==",
|
||||
"dependencies": {
|
||||
"@types/hoist-non-react-statics": "^3.3.1",
|
||||
"hoist-non-react-statics": "^3.3.2",
|
||||
"universal-cookie": "^6.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">= 16.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-copy-to-clipboard": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-copy-to-clipboard/-/react-copy-to-clipboard-5.1.0.tgz",
|
||||
|
@ -10368,6 +10387,15 @@
|
|||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/universal-cookie": {
|
||||
"version": "6.1.1",
|
||||
"resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-6.1.1.tgz",
|
||||
"integrity": "sha512-33S9x3CpdUnnjwTNs2Fgc41WGve2tdLtvaK2kPSbZRc5pGpz2vQFbRWMxlATsxNNe/Cy8SzmnmbuBM85jpZPtA==",
|
||||
"dependencies": {
|
||||
"@types/cookie": "^0.5.1",
|
||||
"cookie": "^0.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/unpipe": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||
|
|
10
package.json
10
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "cosmos-server",
|
||||
"version": "0.9.21",
|
||||
"version": "0.10.0-unstable31",
|
||||
"description": "",
|
||||
"main": "test-server.js",
|
||||
"bugs": {
|
||||
|
@ -36,6 +36,7 @@
|
|||
"react": "^18.2.0",
|
||||
"react-apexcharts": "^1.4.0",
|
||||
"react-color": "^2.19.3",
|
||||
"react-cookie": "^6.1.1",
|
||||
"react-copy-to-clipboard": "^5.1.0",
|
||||
"react-device-detect": "^2.2.2",
|
||||
"react-dom": "^18.2.0",
|
||||
|
@ -63,13 +64,12 @@
|
|||
"scripts": {
|
||||
"client": "vite",
|
||||
"client-build": "vite build --base=/cosmos-ui/",
|
||||
"start": "env CONFIG_FILE=./config_dev.json EZ=UTC ACME_STAGING=true build/cosmos",
|
||||
"start": "env COSMOS_CONFIG_FOLDER=/mnt/e/work/Cosmos-Server/zz_test_config/ CONFIG_FILE=./config_dev.json EZ=UTC ACME_STAGING=true build/cosmos",
|
||||
"build": "sh build.sh",
|
||||
"dev": "npm run build && npm run start",
|
||||
"dockerdevbuild": "sh build.sh && docker build -f dockerfile.local --tag cosmos-dev .",
|
||||
"dockerdevrun": "docker stop cosmos-dev; docker rm cosmos-dev; docker run -d -p 7200:443 -p 80:80 -p 443:443 -e DOCKER_HOST=tcp://host.docker.internal:2375 -e COSMOS_MONGODB=$MONGODB -e COSMOS_LOG_LEVEL=DEBUG -v /:/mnt/host --restart=unless-stopped -h cosmos-dev --name cosmos-dev cosmos-dev",
|
||||
"dockerdev": "npm run dockerdevbuild && npm run dockerdevrun",
|
||||
"dockerdevclient": "npm run client-build && npm run dockerdevbuild && npm run dockerdevrun",
|
||||
"dockerdevrun": "docker stop cosmos-dev; docker rm cosmos-dev; docker run --cap-add NET_ADMIN -d -p 7200:443 -p 80:80 -p 53:53 -p 443:443 -p 4242:4242 -e DOCKER_HOST=tcp://host.docker.internal:2375 -e COSMOS_MONGODB=$MONGODB -e COSMOS_LOG_LEVEL=DEBUG -v /:/mnt/host --restart=unless-stopped -h cosmos-dev --name cosmos-dev cosmos-dev",
|
||||
"dockerdev": "npm run client-build && npm run dockerdevbuild && npm run dockerdevrun",
|
||||
"demo": "vite build --base=/cosmos-ui/ --mode demo",
|
||||
"devdemo": "vite --mode demo"
|
||||
},
|
||||
|
|
10
readme.md
10
readme.md
|
@ -7,8 +7,7 @@
|
|||
<p align="center"><a href="https://github.com/DrMxrcy"><img src="https://avatars.githubusercontent.com/DrMxrcy" style="border-radius:48px" width="48" height="48" alt="null" title="null" /></a>
|
||||
<a href="https://github.com/soldier1"><img src="https://avatars.githubusercontent.com/soldier1" style="border-radius:48px" width="48" height="48" alt="null" title="null" /></a>
|
||||
<a href="https://github.com/devcircus"><img src="https://avatars.githubusercontent.com/devcircus" style="border-radius:48px" width="48" height="48" alt="Clayton Stone" title="Clayton Stone" /></a>
|
||||
<a href="https://github.com/BillyDas"><img src="https://avatars.githubusercontent.com/BillyDas" style="border-radius:48px" width="48" height="48" alt="Billy Das" title="Billy Das" /></a>
|
||||
<a href="https://github.com/Serph91P"><img src="https://avatars.githubusercontent.com/Serph91P" style="border-radius:48px" width="48" height="48" alt="Seraph91P" title="Seraph91P" /></a>
|
||||
<a href="https://github.com/BlackrazorNZ"><img src="https://avatars.githubusercontent.com/BlackrazorNZ" style="border-radius:48px" width="48" height="48" alt="null" title="null" /></a>
|
||||
</p><!-- /sponsors -->
|
||||
|
||||
---
|
||||
|
@ -45,6 +44,7 @@ Cosmos is a:
|
|||
* **Reverse-Proxy** 🔄🔗 Targeting containers, other servers, or serving static folders / SPA with **automatic HTTPS**, and a **nice UI**
|
||||
* **Authentication Server** 👦👩 With strong security, **multi-factor authentication** and multiple strategies (**OpenId**, forward headers, HTML)
|
||||
* **Container manager** 🐋🔧 To easily manage your containers and their settings, keep them up to date as well as audit their security. Includes docker-compose support!
|
||||
* **VPN** 🌐🔒 To securely access your applications from anywhere, without having to open ports on your router.
|
||||
* **Identity Provider** 👦👩 To easily manage your users, **invite your friends and family** to your applications without awkardly sharing credentials. Let them request a password change with an email rather than having you unlock their account manually!
|
||||
* **SmartShield technology** 🧠🛡 Automatically secure your applications without manual adjustments (see below for more details). Includes anti-bot and anti-DDOS strategies.
|
||||
|
||||
|
@ -146,15 +146,17 @@ Note that **you are allowed** to use it to host a monetized business website, a
|
|||
Installation is simple using Docker:
|
||||
|
||||
```
|
||||
docker run -d -p 80:80 -p 443:443 --privileged --name cosmos-server -h cosmos-server --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v /:/mnt/host -v /var/lib/cosmos:/config azukaar/cosmos-server:latest
|
||||
docker run -d -p 80:80 -p 443:443 -p 4242:4242/udp --privileged --name cosmos-server -h cosmos-server --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v /:/mnt/host -v /var/lib/cosmos:/config azukaar/cosmos-server:latest
|
||||
```
|
||||
|
||||
in this command, `-v /:/mnt/host` is optional and allow to manage folders from Cosmos, you can remove it if you don't want it but you will have to create your container's bind folders manually.
|
||||
|
||||
`--privileged` is also optional, but it is required if you use hardening software like AppArmor or SELinux, as they restrict access to the docker socket.
|
||||
`--privileged` is also optional, but it is required if you use hardening software like AppArmor or SELinux, as they restrict access to the docker socket. It is also required for Constellation to work. If you don't want to use it, you can add the following capabilities: NET_ADMIN for Constellation.
|
||||
|
||||
Once installed, simply go to `http://your-server-ip` and follow the instructions of the setup wizard.
|
||||
|
||||
Port 4242 is a UDP port used for the Constellation VPN.
|
||||
|
||||
make sure you expose the right ports (by default 80 / 443). It is best to keep those ports intacts, as Cosmos is meant to run as your reverse proxy. Trying to setup Cosmos behind another reverse proxy is possible but will only create headaches.
|
||||
|
||||
You also need to keep the docker socket mounted, as Cosmos needs to be able to manage your containers.
|
||||
|
|
|
@ -54,7 +54,7 @@ func UploadBackground(w http.ResponseWriter, req *http.Request) {
|
|||
}
|
||||
|
||||
// create a new file in the config directory
|
||||
dst, err := os.Create("/config/background" + ext)
|
||||
dst, err := os.Create(utils.CONFIGFOLDER + "background" + ext)
|
||||
if err != nil {
|
||||
utils.HTTPError(w, "Error creating destination file", http.StatusInternalServerError, "FILE004")
|
||||
return
|
||||
|
@ -99,7 +99,7 @@ func GetBackground(w http.ResponseWriter, req *http.Request) {
|
|||
|
||||
if(req.Method == "GET") {
|
||||
// get the background image
|
||||
bg, err := ioutil.ReadFile("/config/background." + ext)
|
||||
bg, err := ioutil.ReadFile(utils.CONFIGFOLDER + "background." + ext)
|
||||
if err != nil {
|
||||
utils.HTTPError(w, "Error reading background image", http.StatusInternalServerError, "FILE003")
|
||||
return
|
||||
|
|
|
@ -42,6 +42,7 @@ func ConfigApiGet(w http.ResponseWriter, req *http.Request) {
|
|||
"data": config,
|
||||
"updates": utils.UpdateAvailable,
|
||||
"hostname": os.Getenv("HOSTNAME"),
|
||||
"isAdmin": isAdmin,
|
||||
})
|
||||
} else {
|
||||
utils.Error("SettingGet: Method not allowed" + req.Method, nil)
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"encoding/json"
|
||||
"github.com/azukaar/cosmos-server/src/utils"
|
||||
"github.com/azukaar/cosmos-server/src/authorizationserver"
|
||||
"github.com/azukaar/cosmos-server/src/constellation"
|
||||
)
|
||||
|
||||
func ConfigApiSet(w http.ResponseWriter, req *http.Request) {
|
||||
|
@ -43,6 +44,7 @@ func ConfigApiSet(w http.ResponseWriter, req *http.Request) {
|
|||
utils.DisconnectDB()
|
||||
authorizationserver.Init()
|
||||
utils.RestartHTTPServer()
|
||||
constellation.RestartNebula()
|
||||
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"status": "OK",
|
||||
|
|
190
src/constellation/DNS.go
Normal file
190
src/constellation/DNS.go
Normal file
|
@ -0,0 +1,190 @@
|
|||
package constellation
|
||||
|
||||
import (
|
||||
"time"
|
||||
"strconv"
|
||||
"strings"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/azukaar/cosmos-server/src/utils"
|
||||
)
|
||||
|
||||
var DNSBlacklist = map[string]bool{}
|
||||
|
||||
func externalLookup(client *dns.Client, r *dns.Msg, serverAddr string) (*dns.Msg, time.Duration, error) {
|
||||
rCopy := r.Copy() // Create a copy of the request to forward
|
||||
rCopy.Id = dns.Id() // Assign a new ID for the forwarded request
|
||||
|
||||
// Enable DNSSEC
|
||||
rCopy.SetEdns0(4096, true)
|
||||
rCopy.CheckingDisabled = false
|
||||
rCopy.MsgHdr.AuthenticatedData = true
|
||||
|
||||
return client.Exchange(rCopy, serverAddr)
|
||||
}
|
||||
|
||||
func handleDNSRequest(w dns.ResponseWriter, r *dns.Msg) {
|
||||
config := utils.GetMainConfig()
|
||||
DNSFallback := config.ConstellationConfig.DNSFallback
|
||||
|
||||
if DNSFallback == "" {
|
||||
DNSFallback = "8.8.8.8:53"
|
||||
}
|
||||
|
||||
m := new(dns.Msg)
|
||||
m.SetReply(r)
|
||||
m.Authoritative = true
|
||||
|
||||
customHandled := false
|
||||
|
||||
// []string hostnames
|
||||
hostnames := utils.GetAllHostnames(false, true)
|
||||
|
||||
if !customHandled {
|
||||
customDNSEntries := config.ConstellationConfig.CustomDNSEntries
|
||||
|
||||
// Overwrite local hostnames with custom entries
|
||||
for _, q := range r.Question {
|
||||
for _, entry := range customDNSEntries {
|
||||
hostname := entry.Key
|
||||
ip := entry.Value
|
||||
|
||||
if strings.HasSuffix(q.Name, hostname + ".") && q.Qtype == dns.TypeA {
|
||||
utils.Debug("DNS Overwrite " + hostname + " with " + ip)
|
||||
rr, _ := dns.NewRR(q.Name + " A " + ip)
|
||||
m.Answer = append(m.Answer, rr)
|
||||
customHandled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !customHandled {
|
||||
// Overwrite local hostnames with Constellation IP
|
||||
for _, q := range r.Question {
|
||||
utils.Debug("DNS Question " + q.Name)
|
||||
for _, hostname := range hostnames {
|
||||
if strings.HasSuffix(q.Name, hostname + ".") && q.Qtype == dns.TypeA {
|
||||
utils.Debug("DNS Overwrite " + hostname + " with 192.168.201.1")
|
||||
rr, _ := dns.NewRR(q.Name + " A 192.168.201.1")
|
||||
m.Answer = append(m.Answer, rr)
|
||||
customHandled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !customHandled {
|
||||
// Block blacklisted domains
|
||||
for _, q := range r.Question {
|
||||
noDot := strings.TrimSuffix(q.Name, ".")
|
||||
if DNSBlacklist[noDot] {
|
||||
if q.Qtype == dns.TypeA {
|
||||
utils.Debug("DNS Block " + noDot)
|
||||
rr, _ := dns.NewRR(q.Name + " A 0.0.0.0")
|
||||
m.Answer = append(m.Answer, rr)
|
||||
}
|
||||
|
||||
customHandled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If not custom handled, use external DNS
|
||||
if !customHandled {
|
||||
client := new(dns.Client)
|
||||
externalResponse, time, err := externalLookup(client, r, DNSFallback)
|
||||
if err != nil {
|
||||
utils.Error("Failed to forward query:", err)
|
||||
return
|
||||
}
|
||||
utils.Debug("DNS Forwarded DNS query to "+DNSFallback+" in " + time.String())
|
||||
|
||||
externalResponse.Id = r.Id
|
||||
|
||||
m = externalResponse
|
||||
}
|
||||
|
||||
w.WriteMsg(m)
|
||||
}
|
||||
|
||||
func isDomain(domain string) bool {
|
||||
// contains . and at least a letter and no special characters invalid in a domain
|
||||
if strings.Contains(domain, ".") && strings.ContainsAny(domain, "abcdefghijklmnopqrstuvwxyz") && !strings.ContainsAny(domain, " !@#$%^&*()+=[]{}\\|;:'\",/<>?") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func loadRawBlockList(DNSBlacklistRaw string) {
|
||||
DNSBlacklistArray := strings.Split(string(DNSBlacklistRaw), "\n")
|
||||
for _, domain := range DNSBlacklistArray {
|
||||
if domain != "" && !strings.HasPrefix(domain, "#") {
|
||||
splitDomain := strings.Split(domain, " ")
|
||||
if len(splitDomain) == 1 && isDomain(splitDomain[0]) {
|
||||
DNSBlacklist[splitDomain[0]] = true
|
||||
} else if len(splitDomain) == 2 {
|
||||
if isDomain(splitDomain[0]) {
|
||||
DNSBlacklist[splitDomain[0]] = true
|
||||
} else if isDomain(splitDomain[1]) {
|
||||
DNSBlacklist[splitDomain[1]] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func InitDNS() {
|
||||
config := utils.GetMainConfig()
|
||||
DNSPort := config.ConstellationConfig.DNSPort
|
||||
DNSBlockBlacklist := config.ConstellationConfig.DNSBlockBlacklist
|
||||
|
||||
if DNSPort == "" {
|
||||
DNSPort = "53"
|
||||
}
|
||||
|
||||
if DNSBlockBlacklist {
|
||||
DNSBlacklist = map[string]bool{}
|
||||
blacklistPath := utils.CONFIGFOLDER + "dns-blacklist.txt"
|
||||
|
||||
utils.Log("Loading DNS blacklist from " + blacklistPath)
|
||||
|
||||
fileExist := utils.FileExists(blacklistPath)
|
||||
if fileExist {
|
||||
DNSBlacklistRaw, err := ioutil.ReadFile(blacklistPath)
|
||||
if err != nil {
|
||||
utils.Error("Failed to load DNS blacklist", err)
|
||||
} else {
|
||||
loadRawBlockList(string(DNSBlacklistRaw))
|
||||
}
|
||||
} else {
|
||||
utils.Log("No DNS blacklist found")
|
||||
}
|
||||
|
||||
// download additional blocklists from config.DNSAdditionalBlocklists []string
|
||||
for _, url := range config.ConstellationConfig.DNSAdditionalBlocklists {
|
||||
utils.Log("Downloading DNS blacklist from " + url)
|
||||
DNSBlacklistRaw, err := utils.DownloadFile(url)
|
||||
if err != nil {
|
||||
utils.Error("Failed to download DNS blacklist", err)
|
||||
} else {
|
||||
loadRawBlockList(DNSBlacklistRaw)
|
||||
}
|
||||
}
|
||||
|
||||
utils.Log("Loaded " + strconv.Itoa(len(DNSBlacklist)) + " domains")
|
||||
}
|
||||
|
||||
if(!config.ConstellationConfig.DNSDisabled) {
|
||||
go (func() {
|
||||
dns.HandleFunc(".", handleDNSRequest)
|
||||
server := &dns.Server{Addr: ":" + DNSPort, Net: "udp"}
|
||||
|
||||
utils.Log("Starting DNS server on :" + DNSPort)
|
||||
if err := server.ListenAndServe(); err != nil {
|
||||
utils.Fatal("Failed to start server: %s\n", err)
|
||||
}
|
||||
})()
|
||||
}
|
||||
}
|
101
src/constellation/api_devices_block.go
Normal file
101
src/constellation/api_devices_block.go
Normal file
|
@ -0,0 +1,101 @@
|
|||
package constellation
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/azukaar/cosmos-server/src/utils"
|
||||
)
|
||||
|
||||
type DeviceBlockRequestJSON struct {
|
||||
Nickname string `json:"nickname",validate:"required,min=3,max=32,alphanum"`
|
||||
DeviceName string `json:"deviceName",validate:"required,min=3,max=32,alphanum"`
|
||||
Block bool `json:"block",omitempty`
|
||||
}
|
||||
|
||||
func DeviceBlock(w http.ResponseWriter, req *http.Request) {
|
||||
if(req.Method == "POST") {
|
||||
var request DeviceBlockRequestJSON
|
||||
err1 := json.NewDecoder(req.Body).Decode(&request)
|
||||
if err1 != nil {
|
||||
utils.Error("ConstellationDeviceBlocking: Invalid User Request", err1)
|
||||
utils.HTTPError(w, "Device Creation Error",
|
||||
http.StatusInternalServerError, "DB001")
|
||||
return
|
||||
}
|
||||
|
||||
errV := utils.Validate.Struct(request)
|
||||
if errV != nil {
|
||||
utils.Error("DeviceBlocking: Invalid User Request", errV)
|
||||
utils.HTTPError(w, "Device Creation Error: " + errV.Error(),
|
||||
http.StatusInternalServerError, "DB002")
|
||||
return
|
||||
}
|
||||
|
||||
nickname := utils.Sanitize(request.Nickname)
|
||||
deviceName := utils.Sanitize(request.DeviceName)
|
||||
|
||||
if utils.AdminOrItselfOnly(w, req, nickname) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
utils.Log("ConstellationDeviceBlocking: Blocking Device " + deviceName)
|
||||
|
||||
c, errCo := utils.GetCollection(utils.GetRootAppId(), "devices")
|
||||
if errCo != nil {
|
||||
utils.Error("Database Connect", errCo)
|
||||
utils.HTTPError(w, "Database", http.StatusInternalServerError, "DB001")
|
||||
return
|
||||
}
|
||||
|
||||
device := utils.Device{}
|
||||
|
||||
utils.Debug("ConstellationDeviceBlocking: Blocking Device " + deviceName)
|
||||
|
||||
err2 := c.FindOne(nil, map[string]interface{}{
|
||||
"DeviceName": deviceName,
|
||||
"Nickname": nickname,
|
||||
"Blocked": false,
|
||||
}).Decode(&device)
|
||||
|
||||
if err2 == nil {
|
||||
utils.Debug("ConstellationDeviceBlocking: Found Device " + deviceName)
|
||||
|
||||
_, err3 := c.UpdateOne(nil, map[string]interface{}{
|
||||
"DeviceName": deviceName,
|
||||
"Nickname": nickname,
|
||||
}, map[string]interface{}{
|
||||
"$set": map[string]interface{}{
|
||||
"Blocked": request.Block,
|
||||
},
|
||||
})
|
||||
|
||||
if err3 != nil {
|
||||
utils.Error("DeviceBlocking: Error while updating device", err3)
|
||||
utils.HTTPError(w, "Device Creation Error: " + err3.Error(),
|
||||
http.StatusInternalServerError, "DB001")
|
||||
return
|
||||
}
|
||||
|
||||
if request.Block {
|
||||
utils.Log("ConstellationDeviceBlocking: Device " + deviceName + " blocked")
|
||||
} else {
|
||||
utils.Log("ConstellationDeviceBlocking: Device " + deviceName + " unblocked")
|
||||
}
|
||||
} else {
|
||||
utils.Error("DeviceBlocking: Error while finding device", err2)
|
||||
utils.HTTPError(w, "Device Creation Error: " + err2.Error(),
|
||||
http.StatusInternalServerError, "DB001")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"status": "OK",
|
||||
})
|
||||
} else {
|
||||
utils.Error("DeviceBlocking: Method not allowed" + req.Method, nil)
|
||||
utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001")
|
||||
return
|
||||
}
|
||||
}
|
41
src/constellation/api_devices_config.go
Normal file
41
src/constellation/api_devices_config.go
Normal file
|
@ -0,0 +1,41 @@
|
|||
package constellation
|
||||
|
||||
// import (
|
||||
// "net/http"
|
||||
// "encoding/json"
|
||||
// "math/rand"
|
||||
// "time"
|
||||
// "net"
|
||||
|
||||
// "github.com/azukaar/cosmos-server/src/utils"
|
||||
// )
|
||||
|
||||
// func DeviceConfig(w http.ResponseWriter, req *http.Request) {
|
||||
// time.Sleep(time.Duration(rand.Float64()*2)*time.Second)
|
||||
|
||||
// if(req.Method == "GET") {
|
||||
|
||||
// ip, _, err := net.SplitHostPort(req.RemoteAddr)
|
||||
// if err != nil {
|
||||
// http.Error(w, "Invalid request", http.StatusBadRequest)
|
||||
// return
|
||||
// }
|
||||
|
||||
// // get authorization header
|
||||
// auth := req.Header.Get("Authorization")
|
||||
// if auth == "" {
|
||||
// http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||
// return
|
||||
// }
|
||||
|
||||
// // remove "Bearer " from auth header
|
||||
// auth = strings.Replace(auth, "Bearer ", "", 1)
|
||||
|
||||
|
||||
|
||||
// } else {
|
||||
// utils.Error("DeviceConfig: Method not allowed" + req.Method, nil)
|
||||
// utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001")
|
||||
// return
|
||||
// }
|
||||
// }
|
182
src/constellation/api_devices_create.go
Normal file
182
src/constellation/api_devices_create.go
Normal file
|
@ -0,0 +1,182 @@
|
|||
package constellation
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"encoding/json"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
|
||||
"github.com/azukaar/cosmos-server/src/utils"
|
||||
)
|
||||
|
||||
type DeviceCreateRequestJSON struct {
|
||||
DeviceName string `json:"deviceName",validate:"required,min=3,max=32,alphanum"`
|
||||
IP string `json:"ip",validate:"required,ipv4"`
|
||||
PublicKey string `json:"publicKey",omitempty`
|
||||
|
||||
// for devices only
|
||||
Nickname string `json:"nickname",validate:"max=32,alphanum",omitempty`
|
||||
|
||||
// for lighthouse only
|
||||
IsLighthouse bool `json:"isLighthouse",omitempty`
|
||||
IsRelay bool `json:"isRelay",omitempty`
|
||||
PublicHostname string `json:"PublicHostname",omitempty`
|
||||
Port string `json:"port",omitempty`
|
||||
}
|
||||
|
||||
func DeviceCreate(w http.ResponseWriter, req *http.Request) {
|
||||
if(req.Method == "POST") {
|
||||
var request DeviceCreateRequestJSON
|
||||
err1 := json.NewDecoder(req.Body).Decode(&request)
|
||||
if err1 != nil {
|
||||
utils.Error("ConstellationDeviceCreation: Invalid User Request", err1)
|
||||
utils.HTTPError(w, "Device Creation Error",
|
||||
http.StatusInternalServerError, "DC001")
|
||||
return
|
||||
}
|
||||
|
||||
errV := utils.Validate.Struct(request)
|
||||
if errV != nil {
|
||||
utils.Error("DeviceCreation: Invalid User Request", errV)
|
||||
utils.HTTPError(w, "Device Creation Error: " + errV.Error(),
|
||||
http.StatusInternalServerError, "DC002")
|
||||
return
|
||||
}
|
||||
|
||||
nickname := utils.Sanitize(request.Nickname)
|
||||
deviceName := utils.Sanitize(request.DeviceName)
|
||||
APIKey := utils.GenerateRandomString(32)
|
||||
|
||||
if utils.AdminOrItselfOnly(w, req, nickname) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
utils.Log("ConstellationDeviceCreation: Creating Device " + deviceName)
|
||||
|
||||
c, errCo := utils.GetCollection(utils.GetRootAppId(), "devices")
|
||||
if errCo != nil {
|
||||
utils.Error("Database Connect", errCo)
|
||||
utils.HTTPError(w, "Database", http.StatusInternalServerError, "DB001")
|
||||
return
|
||||
}
|
||||
|
||||
device := utils.Device{}
|
||||
|
||||
utils.Debug("ConstellationDeviceCreation: Creating Device " + deviceName)
|
||||
|
||||
err2 := c.FindOne(nil, map[string]interface{}{
|
||||
"DeviceName": deviceName,
|
||||
"Blocked": false,
|
||||
}).Decode(&device)
|
||||
|
||||
if err2 == mongo.ErrNoDocuments {
|
||||
|
||||
cert, key, fingerprint, err := generateNebulaCert(deviceName, request.IP, request.PublicKey, false)
|
||||
|
||||
if err != nil {
|
||||
utils.Error("DeviceCreation: Error while creating Device", err)
|
||||
utils.HTTPError(w, "Device Creation Error: " + err.Error(),
|
||||
http.StatusInternalServerError, "DC001")
|
||||
return
|
||||
}
|
||||
|
||||
if request.IsLighthouse && request.Nickname != "" {
|
||||
utils.Error("DeviceCreation: Lighthouse cannot belong to a user", nil)
|
||||
utils.HTTPError(w, "Device Creation Error: Lighthouse cannot have a nickname",
|
||||
http.StatusInternalServerError, "DC003")
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
utils.Error("DeviceCreation: Error while getting fingerprint", err)
|
||||
utils.HTTPError(w, "Device Creation Error: " + err.Error(),
|
||||
http.StatusInternalServerError, "DC007")
|
||||
return
|
||||
}
|
||||
|
||||
_, err3 := c.InsertOne(nil, map[string]interface{}{
|
||||
"Nickname": nickname,
|
||||
"DeviceName": deviceName,
|
||||
"PublicKey": key,
|
||||
"IP": request.IP,
|
||||
"IsLighthouse": request.IsLighthouse,
|
||||
"IsRelay": request.IsRelay,
|
||||
"PublicHostname": request.PublicHostname,
|
||||
"Port": request.Port,
|
||||
"Fingerprint": fingerprint,
|
||||
"APIKey": APIKey,
|
||||
"Blocked": false,
|
||||
})
|
||||
|
||||
if err3 != nil {
|
||||
utils.Error("DeviceCreation: Error while creating Device", err3)
|
||||
utils.HTTPError(w, "Device Creation Error: " + err.Error(),
|
||||
http.StatusInternalServerError, "DC004")
|
||||
return
|
||||
}
|
||||
|
||||
capki, err := getCApki()
|
||||
if err != nil {
|
||||
utils.Error("DeviceCreation: Error while reading ca.crt", err)
|
||||
utils.HTTPError(w, "Device Creation Error: " + err.Error(),
|
||||
http.StatusInternalServerError, "DC006")
|
||||
return
|
||||
}
|
||||
|
||||
lightHousesList := []utils.ConstellationDevice{}
|
||||
if request.IsLighthouse {
|
||||
lightHousesList, err = GetAllLightHouses()
|
||||
}
|
||||
|
||||
// read configYml from config/nebula.yml
|
||||
configYml, err := getYAMLClientConfig(deviceName, utils.CONFIGFOLDER + "nebula.yml", capki, cert, key, APIKey, utils.ConstellationDevice{
|
||||
Nickname: nickname,
|
||||
DeviceName: deviceName,
|
||||
PublicKey: key,
|
||||
IP: request.IP,
|
||||
IsLighthouse: request.IsLighthouse,
|
||||
IsRelay: request.IsRelay,
|
||||
PublicHostname: request.PublicHostname,
|
||||
Port: request.Port,
|
||||
APIKey: APIKey,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
utils.Error("DeviceCreation: Error while reading config", err)
|
||||
utils.HTTPError(w, "Device Creation Error: " + err.Error(),
|
||||
http.StatusInternalServerError, "DC005")
|
||||
return
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"status": "OK",
|
||||
"data": map[string]interface{}{
|
||||
"Nickname": nickname,
|
||||
"DeviceName": deviceName,
|
||||
"PublicKey": key,
|
||||
"PrivateKey": cert,
|
||||
"IP": request.IP,
|
||||
"Config": configYml,
|
||||
"CA": capki,
|
||||
"IsLighthouse": request.IsLighthouse,
|
||||
"IsRelay": request.IsRelay,
|
||||
"PublicHostname": request.PublicHostname,
|
||||
"Port": request.Port,
|
||||
"LighthousesList": lightHousesList,
|
||||
},
|
||||
})
|
||||
} else if err2 == nil {
|
||||
utils.Error("DeviceCreation: Device already exists", nil)
|
||||
utils.HTTPError(w, "Device name already exists", http.StatusConflict, "DC002")
|
||||
return
|
||||
} else {
|
||||
utils.Error("DeviceCreation: Error while finding device", err2)
|
||||
utils.HTTPError(w, "Device Creation Error: " + err2.Error(),
|
||||
http.StatusInternalServerError, "DC001")
|
||||
return
|
||||
}
|
||||
} else {
|
||||
utils.Error("DeviceCreation: Method not allowed" + req.Method, nil)
|
||||
utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001")
|
||||
return
|
||||
}
|
||||
}
|
18
src/constellation/api_devices_index.go
Normal file
18
src/constellation/api_devices_index.go
Normal file
|
@ -0,0 +1,18 @@
|
|||
package constellation
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"github.com/azukaar/cosmos-server/src/utils"
|
||||
)
|
||||
|
||||
func ConstellationAPIDevices(w http.ResponseWriter, req *http.Request) {
|
||||
if (req.Method == "GET") {
|
||||
DeviceList(w, req)
|
||||
} else if (req.Method == "POST") {
|
||||
DeviceCreate(w, req)
|
||||
} else {
|
||||
utils.Error("UserRoute: Method not allowed" + req.Method, nil)
|
||||
utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001")
|
||||
return
|
||||
}
|
||||
}
|
73
src/constellation/api_devices_list.go
Normal file
73
src/constellation/api_devices_list.go
Normal file
|
@ -0,0 +1,73 @@
|
|||
package constellation
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"encoding/json"
|
||||
|
||||
|
||||
"github.com/azukaar/cosmos-server/src/utils"
|
||||
)
|
||||
|
||||
func DeviceList(w http.ResponseWriter, req *http.Request) {
|
||||
// Check for GET method
|
||||
if req.Method != "GET" {
|
||||
utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP002")
|
||||
return
|
||||
}
|
||||
|
||||
if utils.LoggedInOnly(w, req) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
isAdmin := utils.IsAdmin(req)
|
||||
|
||||
// Connect to the collection
|
||||
c, errCo := utils.GetCollection(utils.GetRootAppId(), "devices")
|
||||
if errCo != nil {
|
||||
utils.Error("Database Connect", errCo)
|
||||
utils.HTTPError(w, "Database", http.StatusInternalServerError, "DB001")
|
||||
return
|
||||
}
|
||||
|
||||
var devices []utils.ConstellationDevice
|
||||
|
||||
// Check if user is an admin
|
||||
if isAdmin {
|
||||
// If admin, get all devices
|
||||
cursor, err := c.Find(nil, map[string]interface{}{})
|
||||
if err != nil {
|
||||
utils.Error("DeviceList: Error fetching devices", err)
|
||||
utils.HTTPError(w, "Error fetching devices", http.StatusInternalServerError, "DL001")
|
||||
return
|
||||
}
|
||||
defer cursor.Close(nil)
|
||||
|
||||
if err = cursor.All(nil, &devices); err != nil {
|
||||
utils.Error("DeviceList: Error decoding devices", err)
|
||||
utils.HTTPError(w, "Error decoding devices", http.StatusInternalServerError, "DL002")
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// If not admin, get user's devices based on their nickname
|
||||
nickname := req.Header.Get("x-cosmos-user")
|
||||
cursor, err := c.Find(nil, map[string]interface{}{"Nickname": nickname})
|
||||
if err != nil {
|
||||
utils.Error("DeviceList: Error fetching devices", err)
|
||||
utils.HTTPError(w, "Error fetching devices", http.StatusInternalServerError, "DL003")
|
||||
return
|
||||
}
|
||||
defer cursor.Close(nil)
|
||||
|
||||
if err = cursor.All(nil, &devices); err != nil {
|
||||
utils.Error("DeviceList: Error decoding devices", err)
|
||||
utils.HTTPError(w, "Error decoding devices", http.StatusInternalServerError, "DL004")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Respond with the list of devices
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"status": "OK",
|
||||
"data": devices,
|
||||
})
|
||||
}
|
99
src/constellation/api_nebula.go
Normal file
99
src/constellation/api_nebula.go
Normal file
|
@ -0,0 +1,99 @@
|
|||
package constellation
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/azukaar/cosmos-server/src/utils"
|
||||
)
|
||||
|
||||
func API_GetConfig(w http.ResponseWriter, req *http.Request) {
|
||||
if utils.AdminOnly(w, req) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if(req.Method == "GET") {
|
||||
// read utils.CONFIGFOLDER + "nebula.yml"
|
||||
config, err := ioutil.ReadFile(utils.CONFIGFOLDER + "nebula.yml")
|
||||
|
||||
if err != nil {
|
||||
utils.Error("SettingGet: error while reading nebula.yml", err)
|
||||
utils.HTTPError(w, "Error while reading nebula.yml", http.StatusInternalServerError, "HTTP002")
|
||||
return
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"status": "OK",
|
||||
"data": string(config),
|
||||
})
|
||||
} else {
|
||||
utils.Error("SettingGet: Method not allowed" + req.Method, nil)
|
||||
utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func API_Restart(w http.ResponseWriter, req *http.Request) {
|
||||
if utils.AdminOnly(w, req) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if(req.Method == "GET") {
|
||||
RestartNebula()
|
||||
|
||||
utils.Log("Constellation: nebula restarted")
|
||||
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"status": "OK",
|
||||
})
|
||||
} else {
|
||||
utils.Error("SettingGet: Method not allowed" + req.Method, nil)
|
||||
utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func API_Reset(w http.ResponseWriter, req *http.Request) {
|
||||
if utils.AdminOnly(w, req) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if(req.Method == "GET") {
|
||||
ResetNebula()
|
||||
|
||||
utils.Log("Constellation: nebula reset")
|
||||
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"status": "OK",
|
||||
})
|
||||
} else {
|
||||
utils.Error("SettingGet: Method not allowed" + req.Method, nil)
|
||||
utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func API_GetLogs(w http.ResponseWriter, req *http.Request) {
|
||||
if utils.AdminOnly(w, req) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if(req.Method == "GET") {
|
||||
logs, err := os.ReadFile(utils.CONFIGFOLDER+"nebula.log")
|
||||
if err != nil {
|
||||
utils.Error("Error reading file:", err)
|
||||
return
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"status": "OK",
|
||||
"data": string(logs),
|
||||
})
|
||||
} else {
|
||||
utils.Error("SettingGet: Method not allowed" + req.Method, nil)
|
||||
utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001")
|
||||
return
|
||||
}
|
||||
}
|
47
src/constellation/api_nebula_connect.go
Normal file
47
src/constellation/api_nebula_connect.go
Normal file
|
@ -0,0 +1,47 @@
|
|||
package constellation
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
|
||||
|
||||
"github.com/azukaar/cosmos-server/src/utils"
|
||||
)
|
||||
|
||||
func API_ConnectToExisting(w http.ResponseWriter, req *http.Request) {
|
||||
if utils.AdminOnly(w, req) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if(req.Method == "POST") {
|
||||
body, err := ioutil.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
utils.Error("API_Restart: Invalid User Request", err)
|
||||
utils.HTTPError(w, "API_Restart Error",
|
||||
http.StatusInternalServerError, "AR001")
|
||||
return
|
||||
}
|
||||
|
||||
config := utils.ReadConfigFromFile()
|
||||
config.ConstellationConfig.Enabled = true
|
||||
config.ConstellationConfig.SlaveMode = true
|
||||
config.ConstellationConfig.DNSDisabled = true
|
||||
// ConstellationHostname =
|
||||
|
||||
// output utils.CONFIGFOLDER + "nebula.yml"
|
||||
err = ioutil.WriteFile(utils.CONFIGFOLDER + "nebula.yml", body, 0644)
|
||||
|
||||
utils.SetBaseMainConfig(config)
|
||||
|
||||
RestartNebula()
|
||||
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"status": "OK",
|
||||
})
|
||||
} else {
|
||||
utils.Error("SettingGet: Method not allowed" + req.Method, nil)
|
||||
utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001")
|
||||
return
|
||||
}
|
||||
}
|
62
src/constellation/index.go
Normal file
62
src/constellation/index.go
Normal file
|
@ -0,0 +1,62 @@
|
|||
package constellation
|
||||
|
||||
import (
|
||||
"github.com/azukaar/cosmos-server/src/utils"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Init() {
|
||||
var err error
|
||||
|
||||
// if date is > 1st of January 2024
|
||||
timeNow := time.Now()
|
||||
if timeNow.Year() > 2024 || (timeNow.Year() == 2024 && timeNow.Month() > 1) {
|
||||
utils.Error("Constellation: this preview version has expired, please update to use the lastest version of Constellation.", nil)
|
||||
// disable constellation
|
||||
configFile := utils.ReadConfigFromFile()
|
||||
configFile.ConstellationConfig.Enabled = false
|
||||
utils.SetBaseMainConfig(configFile)
|
||||
return
|
||||
}
|
||||
|
||||
// if Constellation is enabled
|
||||
if utils.GetMainConfig().ConstellationConfig.Enabled {
|
||||
if !utils.GetMainConfig().ConstellationConfig.SlaveMode {
|
||||
InitConfig()
|
||||
|
||||
utils.Log("Initializing Constellation module...")
|
||||
|
||||
// check if ca.crt exists
|
||||
if _, err = os.Stat(utils.CONFIGFOLDER + "ca.crt"); os.IsNotExist(err) {
|
||||
utils.Log("Constellation: ca.crt not found, generating...")
|
||||
// generate ca.crt
|
||||
generateNebulaCACert("Cosmos - " + utils.GetMainConfig().ConstellationConfig.ConstellationHostname)
|
||||
}
|
||||
|
||||
// check if cosmos.crt exists
|
||||
if _, err := os.Stat(utils.CONFIGFOLDER + "cosmos.crt"); os.IsNotExist(err) {
|
||||
utils.Log("Constellation: cosmos.crt not found, generating...")
|
||||
// generate cosmos.crt
|
||||
generateNebulaCert("cosmos", "192.168.201.1/24", "", true)
|
||||
}
|
||||
|
||||
// export nebula.yml
|
||||
utils.Log("Constellation: exporting nebula.yml...")
|
||||
err := ExportConfigToYAML(utils.GetMainConfig().ConstellationConfig, utils.CONFIGFOLDER + "nebula.yml")
|
||||
|
||||
if err != nil {
|
||||
utils.Error("Constellation: error while exporting nebula.yml", err)
|
||||
}
|
||||
}
|
||||
|
||||
// start nebula
|
||||
utils.Log("Constellation: starting nebula...")
|
||||
err = startNebulaInBackground()
|
||||
if err != nil {
|
||||
utils.Error("Constellation: error while starting nebula", err)
|
||||
}
|
||||
|
||||
utils.Log("Constellation module initialized")
|
||||
}
|
||||
}
|
542
src/constellation/nebula.go
Normal file
542
src/constellation/nebula.go
Normal file
|
@ -0,0 +1,542 @@
|
|||
package constellation
|
||||
|
||||
import (
|
||||
"github.com/azukaar/cosmos-server/src/utils"
|
||||
"os/exec"
|
||||
"os"
|
||||
"fmt"
|
||||
"errors"
|
||||
"runtime"
|
||||
"sync"
|
||||
"gopkg.in/yaml.v2"
|
||||
"strings"
|
||||
"io/ioutil"
|
||||
"strconv"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"github.com/natefinch/lumberjack"
|
||||
)
|
||||
|
||||
var logBuffer *lumberjack.Logger
|
||||
|
||||
var (
|
||||
process *exec.Cmd
|
||||
processMux sync.Mutex
|
||||
)
|
||||
|
||||
func binaryToRun() string {
|
||||
if runtime.GOARCH == "arm" || runtime.GOARCH == "arm64" {
|
||||
return "./nebula-arm"
|
||||
}
|
||||
return "./nebula"
|
||||
}
|
||||
|
||||
func startNebulaInBackground() error {
|
||||
processMux.Lock()
|
||||
defer processMux.Unlock()
|
||||
|
||||
if process != nil {
|
||||
return errors.New("nebula is already running")
|
||||
}
|
||||
|
||||
logBuffer = &lumberjack.Logger{
|
||||
Filename: utils.CONFIGFOLDER+"nebula.log",
|
||||
MaxSize: 1, // megabytes
|
||||
MaxBackups: 1,
|
||||
MaxAge: 15, //days
|
||||
Compress: false,
|
||||
}
|
||||
|
||||
process = exec.Command(binaryToRun(), "-config", utils.CONFIGFOLDER+"nebula.yml")
|
||||
|
||||
// Set up multi-writer for stderr
|
||||
process.Stderr = io.MultiWriter(logBuffer, os.Stderr)
|
||||
|
||||
if utils.LoggingLevelLabels[utils.GetMainConfig().LoggingLevel] == utils.DEBUG {
|
||||
// Set up multi-writer for stdout if in debug mode
|
||||
process.Stdout = io.MultiWriter(logBuffer, os.Stdout)
|
||||
} else {
|
||||
process.Stdout = io.MultiWriter(logBuffer)
|
||||
}
|
||||
|
||||
// Start the process in the background
|
||||
if err := process.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
utils.Log(fmt.Sprintf("%s started with PID %d\n", binaryToRun(), process.Process.Pid))
|
||||
return nil
|
||||
}
|
||||
|
||||
func stop() error {
|
||||
processMux.Lock()
|
||||
defer processMux.Unlock()
|
||||
|
||||
if process == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := process.Process.Kill(); err != nil {
|
||||
return err
|
||||
}
|
||||
process = nil
|
||||
utils.Log("Stopped nebula.")
|
||||
return nil
|
||||
}
|
||||
|
||||
func RestartNebula() {
|
||||
stop()
|
||||
Init()
|
||||
}
|
||||
|
||||
func ResetNebula() error {
|
||||
stop()
|
||||
utils.Log("Resetting nebula...")
|
||||
os.RemoveAll(utils.CONFIGFOLDER + "nebula.yml")
|
||||
os.RemoveAll(utils.CONFIGFOLDER + "ca.crt")
|
||||
os.RemoveAll(utils.CONFIGFOLDER + "ca.key")
|
||||
os.RemoveAll(utils.CONFIGFOLDER + "cosmos.crt")
|
||||
os.RemoveAll(utils.CONFIGFOLDER + "cosmos.key")
|
||||
// remove everything in db
|
||||
|
||||
c, err := utils.GetCollection(utils.GetRootAppId(), "devices")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = c.DeleteMany(nil, map[string]interface{}{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config := utils.ReadConfigFromFile()
|
||||
config.ConstellationConfig.Enabled = false
|
||||
config.ConstellationConfig.SlaveMode = false
|
||||
config.ConstellationConfig.DNSDisabled = false
|
||||
|
||||
utils.SetBaseMainConfig(config)
|
||||
|
||||
Init()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetAllLightHouses() ([]utils.ConstellationDevice, error) {
|
||||
c, err := utils.GetCollection(utils.GetRootAppId(), "devices")
|
||||
if err != nil {
|
||||
return []utils.ConstellationDevice{}, err
|
||||
}
|
||||
|
||||
var devices []utils.ConstellationDevice
|
||||
|
||||
cursor, err := c.Find(nil, map[string]interface{}{
|
||||
"IsLighthouse": true,
|
||||
"Blocked": false,
|
||||
})
|
||||
cursor.All(nil, &devices)
|
||||
|
||||
if err != nil {
|
||||
return []utils.ConstellationDevice{}, err
|
||||
}
|
||||
|
||||
return devices, nil
|
||||
}
|
||||
|
||||
func GetBlockedDevices() ([]utils.ConstellationDevice, error) {
|
||||
c, err := utils.GetCollection(utils.GetRootAppId(), "devices")
|
||||
if err != nil {
|
||||
return []utils.ConstellationDevice{}, err
|
||||
}
|
||||
|
||||
var devices []utils.ConstellationDevice
|
||||
|
||||
cursor, err := c.Find(nil, map[string]interface{}{
|
||||
"Blocked": true,
|
||||
})
|
||||
cursor.All(nil, &devices)
|
||||
|
||||
if err != nil {
|
||||
return []utils.ConstellationDevice{}, err
|
||||
}
|
||||
|
||||
return devices, nil
|
||||
}
|
||||
|
||||
func cleanIp(ip string) string {
|
||||
return strings.Split(ip, "/")[0]
|
||||
}
|
||||
|
||||
func ExportConfigToYAML(overwriteConfig utils.ConstellationConfig, outputPath string) error {
|
||||
// Combine defaultConfig and overwriteConfig
|
||||
finalConfig := NebulaDefaultConfig
|
||||
|
||||
if !overwriteConfig.PrivateNode {
|
||||
finalConfig.StaticHostMap = map[string][]string{
|
||||
"192.168.201.1": []string{
|
||||
utils.GetMainConfig().ConstellationConfig.ConstellationHostname + ":4242",
|
||||
},
|
||||
}
|
||||
} else {
|
||||
finalConfig.StaticHostMap = map[string][]string{}
|
||||
}
|
||||
|
||||
// for each lighthouse
|
||||
lh, err := GetAllLightHouses()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, l := range lh {
|
||||
finalConfig.StaticHostMap[cleanIp(l.IP)] = []string{
|
||||
l.PublicHostname + ":" + l.Port,
|
||||
}
|
||||
}
|
||||
|
||||
// add blocked devices
|
||||
blockedDevices, err := GetBlockedDevices()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, d := range blockedDevices {
|
||||
finalConfig.PKI.Blocklist = append(finalConfig.PKI.Blocklist, d.Fingerprint)
|
||||
}
|
||||
|
||||
finalConfig.Lighthouse.AMLighthouse = !overwriteConfig.PrivateNode
|
||||
|
||||
// add other lighthouses
|
||||
finalConfig.Lighthouse.Hosts = []string{}
|
||||
for _, l := range lh {
|
||||
finalConfig.Lighthouse.Hosts = append(finalConfig.Lighthouse.Hosts, cleanIp(l.IP))
|
||||
}
|
||||
|
||||
finalConfig.Relay.AMRelay = overwriteConfig.NebulaConfig.Relay.AMRelay
|
||||
|
||||
finalConfig.Relay.Relays = []string{}
|
||||
for _, l := range lh {
|
||||
if l.IsRelay {
|
||||
finalConfig.Relay.Relays = append(finalConfig.Relay.Relays, cleanIp(l.IP))
|
||||
}
|
||||
}
|
||||
|
||||
// Marshal the combined config to YAML
|
||||
yamlData, err := yaml.Marshal(finalConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// delete nebula.yml if exists
|
||||
if _, err := os.Stat(outputPath); err == nil {
|
||||
os.Remove(outputPath)
|
||||
}
|
||||
|
||||
// Write YAML data to the specified file
|
||||
yamlFile, err := os.Create(outputPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer yamlFile.Close()
|
||||
|
||||
_, err = yamlFile.Write(yamlData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getYAMLClientConfig(name, configPath, capki, cert, key, APIKey string, device utils.ConstellationDevice) (string, error) {
|
||||
utils.Log("Exporting YAML config for " + name + " with file " + configPath)
|
||||
|
||||
// Read the YAML config file
|
||||
yamlData, err := ioutil.ReadFile(configPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Unmarshal the YAML data into a map interface
|
||||
var configMap map[string]interface{}
|
||||
err = yaml.Unmarshal(yamlData, &configMap)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
lh, err := GetAllLightHouses()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if staticHostMap, ok := configMap["static_host_map"].(map[interface{}]interface{}); ok {
|
||||
if !utils.GetMainConfig().ConstellationConfig.PrivateNode {
|
||||
staticHostMap["192.168.201.1"] = []string{
|
||||
utils.GetMainConfig().ConstellationConfig.ConstellationHostname + ":4242",
|
||||
}
|
||||
}
|
||||
|
||||
for _, l := range lh {
|
||||
staticHostMap[cleanIp(l.IP)] = []string{
|
||||
l.PublicHostname + ":" + l.Port,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return "", errors.New("static_host_map not found in nebula.yml")
|
||||
}
|
||||
|
||||
// set lightHouse
|
||||
if lighthouseMap, ok := configMap["lighthouse"].(map[interface{}]interface{}); ok {
|
||||
lighthouseMap["am_lighthouse"] = device.IsLighthouse
|
||||
|
||||
lighthouseMap["hosts"] = []string{}
|
||||
if !utils.GetMainConfig().ConstellationConfig.PrivateNode {
|
||||
lighthouseMap["hosts"] = append(lighthouseMap["hosts"].([]string), "192.168.201.1")
|
||||
}
|
||||
|
||||
for _, l := range lh {
|
||||
if cleanIp(l.IP) != cleanIp(device.IP) {
|
||||
lighthouseMap["hosts"] = append(lighthouseMap["hosts"].([]string), cleanIp(l.IP))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return "", errors.New("lighthouse not found in nebula.yml")
|
||||
}
|
||||
|
||||
if pkiMap, ok := configMap["pki"].(map[interface{}]interface{}); ok {
|
||||
pkiMap["ca"] = capki
|
||||
pkiMap["cert"] = cert
|
||||
pkiMap["key"] = key
|
||||
} else {
|
||||
return "", errors.New("pki not found in nebula.yml")
|
||||
}
|
||||
|
||||
if relayMap, ok := configMap["relay"].(map[interface{}]interface{}); ok {
|
||||
relayMap["am_relay"] = device.IsRelay && device.IsLighthouse
|
||||
relayMap["relays"] = []string{}
|
||||
if utils.GetMainConfig().ConstellationConfig.NebulaConfig.Relay.AMRelay {
|
||||
relayMap["relays"] = append(relayMap["relays"].([]string), "192.168.201.1")
|
||||
}
|
||||
|
||||
for _, l := range lh {
|
||||
if l.IsRelay && l.IsLighthouse && cleanIp(l.IP) != cleanIp(device.IP) {
|
||||
relayMap["relays"] = append(relayMap["relays"].([]string), cleanIp(l.IP))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return "", errors.New("relay not found in nebula.yml")
|
||||
}
|
||||
|
||||
if listen, ok := configMap["listen"].(map[interface{}]interface{}); ok {
|
||||
if device.Port != "" {
|
||||
listen["port"] = device.Port
|
||||
} else {
|
||||
listen["port"] = "4242"
|
||||
}
|
||||
} else {
|
||||
return "", errors.New("listen not found in nebula.yml")
|
||||
}
|
||||
|
||||
configMap["constellation_device_name"] = name
|
||||
configMap["constellation_local_dns_overwrite"] = true
|
||||
configMap["constellation_local_dns_overwrite_address"] = "192.168.201.1"
|
||||
configMap["constellation_public_hostname"] = device.PublicHostname
|
||||
configMap["constellation_api_key"] = APIKey
|
||||
|
||||
// export configMap as YML
|
||||
yamlData, err = yaml.Marshal(configMap)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(yamlData), nil
|
||||
}
|
||||
|
||||
func getCApki() (string, error) {
|
||||
// read config/ca.crt
|
||||
caCrt, err := ioutil.ReadFile(utils.CONFIGFOLDER + "ca.crt")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(caCrt), nil
|
||||
}
|
||||
|
||||
func killAllNebulaInstances() error {
|
||||
processMux.Lock()
|
||||
defer processMux.Unlock()
|
||||
|
||||
cmd := exec.Command("ps", "-e", "-o", "pid,command")
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
lines := strings.Split(string(output), "\n")
|
||||
for _, line := range lines {
|
||||
if strings.Contains(line, binaryToRun()) {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) > 1 {
|
||||
pid := fields[0]
|
||||
pidInt, _ := strconv.Atoi(pid)
|
||||
process, err := os.FindProcess(pidInt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = process.Kill()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
utils.Log(fmt.Sprintf("Killed Nebula instance with PID %s\n", pid))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetCertFingerprint(certPath string) (string, error) {
|
||||
// nebula-cert print -json
|
||||
var cmd *exec.Cmd
|
||||
|
||||
cmd = exec.Command(binaryToRun() + "-cert",
|
||||
"print",
|
||||
"-json",
|
||||
"-path", certPath,
|
||||
)
|
||||
|
||||
// capture and parse output
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
utils.Error("Error while printing cert", err)
|
||||
}
|
||||
|
||||
var certInfo map[string]interface{}
|
||||
err = json.Unmarshal(output, &certInfo)
|
||||
if err != nil {
|
||||
utils.Error("Error while unmarshalling cert information", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Extract fingerprint, replace "fingerprint" with the actual key where the fingerprint is stored in the JSON output
|
||||
fingerprint, ok := certInfo["fingerprint"].(string)
|
||||
if !ok {
|
||||
utils.Error("Fingerprint not found or not a string", nil)
|
||||
return "", errors.New("fingerprint not found or not a string")
|
||||
}
|
||||
|
||||
return fingerprint, nil
|
||||
}
|
||||
|
||||
func generateNebulaCert(name, ip, PK string, saveToFile bool) (string, string, string, error) {
|
||||
// Run the nebula-cert command
|
||||
var cmd *exec.Cmd
|
||||
|
||||
if(PK == "") {
|
||||
cmd = exec.Command(binaryToRun() + "-cert",
|
||||
"sign",
|
||||
"-ca-crt", utils.CONFIGFOLDER + "ca.crt",
|
||||
"-ca-key", utils.CONFIGFOLDER + "ca.key",
|
||||
"-name", name,
|
||||
"-ip", ip,
|
||||
)
|
||||
} else {
|
||||
// write PK to temp.cert
|
||||
err := ioutil.WriteFile("./temp.key", []byte(PK), 0644)
|
||||
if err != nil {
|
||||
return "", "", "", fmt.Errorf("failed to write temp.key: %s", err)
|
||||
}
|
||||
cmd = exec.Command(binaryToRun() + "-cert",
|
||||
"sign",
|
||||
"-ca-crt", utils.CONFIGFOLDER + "ca.crt",
|
||||
"-ca-key", utils.CONFIGFOLDER + "ca.key",
|
||||
"-name", name,
|
||||
"-ip", ip,
|
||||
"-in-pub", "./temp.key",
|
||||
)
|
||||
// delete temp.key
|
||||
defer os.Remove("./temp.key")
|
||||
}
|
||||
|
||||
utils.Debug(cmd.String())
|
||||
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
if utils.LoggingLevelLabels[utils.GetMainConfig().LoggingLevel] == utils.DEBUG {
|
||||
cmd.Stdout = os.Stdout
|
||||
} else {
|
||||
cmd.Stdout = nil
|
||||
}
|
||||
|
||||
cmd.Run()
|
||||
|
||||
if cmd.ProcessState.ExitCode() != 0 {
|
||||
return "", "", "", fmt.Errorf("nebula-cert exited with an error, check the Cosmos logs")
|
||||
}
|
||||
|
||||
// Read the generated certificate and key files
|
||||
certPath := fmt.Sprintf("./%s.crt", name)
|
||||
keyPath := fmt.Sprintf("./%s.key", name)
|
||||
|
||||
utils.Debug("Reading certificate from " + certPath)
|
||||
utils.Debug("Reading key from " + keyPath)
|
||||
|
||||
fingerprint, err := GetCertFingerprint(certPath)
|
||||
if err != nil {
|
||||
return "", "", "", fmt.Errorf("failed to get certificate fingerprint: %s", err)
|
||||
}
|
||||
|
||||
certContent, errCert := ioutil.ReadFile(certPath)
|
||||
if errCert != nil {
|
||||
return "", "", "", fmt.Errorf("failed to read certificate file: %s", errCert)
|
||||
}
|
||||
|
||||
keyContent, errKey := ioutil.ReadFile(keyPath)
|
||||
if errKey != nil {
|
||||
return "", "", "", fmt.Errorf("failed to read key file: %s", errKey)
|
||||
}
|
||||
|
||||
if saveToFile {
|
||||
cmd = exec.Command("mv", certPath, utils.CONFIGFOLDER + name + ".crt")
|
||||
utils.Debug(cmd.String())
|
||||
cmd.Run()
|
||||
cmd = exec.Command("mv", keyPath, utils.CONFIGFOLDER + name + ".key")
|
||||
utils.Debug(cmd.String())
|
||||
cmd.Run()
|
||||
} else {
|
||||
// Delete the generated certificate and key files
|
||||
if err := os.Remove(certPath); err != nil {
|
||||
return "", "", "", fmt.Errorf("failed to delete certificate file: %s", err)
|
||||
}
|
||||
|
||||
if err := os.Remove(keyPath); err != nil {
|
||||
return "", "", "", fmt.Errorf("failed to delete key file: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
return string(certContent), string(keyContent), fingerprint, nil
|
||||
}
|
||||
|
||||
func generateNebulaCACert(name string) (error) {
|
||||
// Run the nebula-cert command to generate CA certificate and key
|
||||
cmd := exec.Command(binaryToRun() + "-cert", "ca", "-name", "\""+name+"\"")
|
||||
|
||||
utils.Debug(cmd.String())
|
||||
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
if utils.LoggingLevelLabels[utils.GetMainConfig().LoggingLevel] == utils.DEBUG {
|
||||
cmd.Stdout = os.Stdout
|
||||
} else {
|
||||
cmd.Stdout = nil
|
||||
}
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("nebula-cert error: %s", err)
|
||||
}
|
||||
|
||||
// copy to /config/ca.*
|
||||
cmd = exec.Command("mv", "./ca.crt", utils.CONFIGFOLDER + "ca.crt")
|
||||
cmd.Run()
|
||||
cmd = exec.Command("mv", "./ca.key", utils.CONFIGFOLDER + "ca.key")
|
||||
cmd.Run()
|
||||
|
||||
return nil
|
||||
}
|
113
src/constellation/nebula_default.go
Normal file
113
src/constellation/nebula_default.go
Normal file
|
@ -0,0 +1,113 @@
|
|||
package constellation
|
||||
|
||||
import (
|
||||
"github.com/azukaar/cosmos-server/src/utils"
|
||||
)
|
||||
|
||||
var NebulaDefaultConfig utils.NebulaConfig
|
||||
|
||||
func InitConfig() {
|
||||
NebulaDefaultConfig = utils.NebulaConfig {
|
||||
PKI: struct {
|
||||
CA string `yaml:"ca"`
|
||||
Cert string `yaml:"cert"`
|
||||
Key string `yaml:"key"`
|
||||
Blocklist []string `yaml:"blocklist"`
|
||||
}{
|
||||
CA: utils.CONFIGFOLDER + "ca.crt",
|
||||
Cert: utils.CONFIGFOLDER + "cosmos.crt",
|
||||
Key: utils.CONFIGFOLDER + "cosmos.key",
|
||||
Blocklist: []string{},
|
||||
},
|
||||
StaticHostMap: map[string][]string{
|
||||
|
||||
},
|
||||
Lighthouse: struct {
|
||||
AMLighthouse bool `yaml:"am_lighthouse"`
|
||||
Interval int `yaml:"interval"`
|
||||
Hosts []string `yaml:"hosts"`
|
||||
}{
|
||||
AMLighthouse: true,
|
||||
Interval: 60,
|
||||
Hosts: []string{},
|
||||
},
|
||||
Listen: struct {
|
||||
Host string `yaml:"host"`
|
||||
Port int `yaml:"port"`
|
||||
}{
|
||||
Host: "0.0.0.0",
|
||||
Port: 4242,
|
||||
},
|
||||
Punchy: struct {
|
||||
Punch bool `yaml:"punch"`
|
||||
Respond bool `yaml:"respond"`
|
||||
}{
|
||||
Punch: true,
|
||||
Respond: true,
|
||||
},
|
||||
Relay: struct {
|
||||
AMRelay bool `yaml:"am_relay"`
|
||||
UseRelays bool `yaml:"use_relays"`
|
||||
Relays []string `yaml:"relays"`
|
||||
}{
|
||||
AMRelay: true,
|
||||
UseRelays: true,
|
||||
Relays: []string{},
|
||||
},
|
||||
TUN: struct {
|
||||
Disabled bool `yaml:"disabled"`
|
||||
Dev string `yaml:"dev"`
|
||||
DropLocalBroadcast bool `yaml:"drop_local_broadcast"`
|
||||
DropMulticast bool `yaml:"drop_multicast"`
|
||||
TxQueue int `yaml:"tx_queue"`
|
||||
MTU int `yaml:"mtu"`
|
||||
Routes []string `yaml:"routes"`
|
||||
UnsafeRoutes []string `yaml:"unsafe_routes"`
|
||||
}{
|
||||
Disabled: false,
|
||||
Dev: "nebula1",
|
||||
DropLocalBroadcast: false,
|
||||
DropMulticast: false,
|
||||
TxQueue: 500,
|
||||
MTU: 1300,
|
||||
Routes: nil,
|
||||
UnsafeRoutes: nil,
|
||||
},
|
||||
Logging: struct {
|
||||
Level string `yaml:"level"`
|
||||
Format string `yaml:"format"`
|
||||
}{
|
||||
Level: "info",
|
||||
Format: "text",
|
||||
},
|
||||
Firewall: struct {
|
||||
OutboundAction string `yaml:"outbound_action"`
|
||||
InboundAction string `yaml:"inbound_action"`
|
||||
Conntrack utils.NebulaConntrackConfig `yaml:"conntrack"`
|
||||
Outbound []utils.NebulaFirewallRule `yaml:"outbound"`
|
||||
Inbound []utils.NebulaFirewallRule `yaml:"inbound"`
|
||||
}{
|
||||
OutboundAction: "drop",
|
||||
InboundAction: "drop",
|
||||
Conntrack: utils.NebulaConntrackConfig{
|
||||
TCPTimeout: "12m",
|
||||
UDPTimeout: "3m",
|
||||
DefaultTimeout: "10m",
|
||||
},
|
||||
Outbound: []utils.NebulaFirewallRule {
|
||||
utils.NebulaFirewallRule {
|
||||
Host: "any",
|
||||
Port: "any",
|
||||
Proto: "any",
|
||||
},
|
||||
},
|
||||
Inbound: []utils.NebulaFirewallRule {
|
||||
utils.NebulaFirewallRule {
|
||||
Host: "any",
|
||||
Port: "any",
|
||||
Proto: "any",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/azukaar/cosmos-server/src/docker"
|
||||
"github.com/azukaar/cosmos-server/src/authorizationserver"
|
||||
"github.com/azukaar/cosmos-server/src/market"
|
||||
"github.com/azukaar/cosmos-server/src/constellation"
|
||||
"github.com/gorilla/mux"
|
||||
"strconv"
|
||||
"time"
|
||||
|
@ -331,6 +332,13 @@ func InitServer() *mux.Router {
|
|||
srapi.HandleFunc("/api/background", UploadBackground)
|
||||
srapi.HandleFunc("/api/background/{ext}", GetBackground)
|
||||
|
||||
srapi.HandleFunc("/api/constellation/devices", constellation.ConstellationAPIDevices)
|
||||
srapi.HandleFunc("/api/constellation/restart", constellation.API_Restart)
|
||||
srapi.HandleFunc("/api/constellation/reset", constellation.API_Reset)
|
||||
srapi.HandleFunc("/api/constellation/connect", constellation.API_ConnectToExisting)
|
||||
srapi.HandleFunc("/api/constellation/config", constellation.API_GetConfig)
|
||||
srapi.HandleFunc("/api/constellation/logs", constellation.API_GetLogs)
|
||||
srapi.HandleFunc("/api/constellation/block", constellation.DeviceBlock)
|
||||
|
||||
if(!config.HTTPConfig.AcceptAllInsecureHostname) {
|
||||
srapi.Use(utils.EnsureHostname)
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/azukaar/cosmos-server/src/utils"
|
||||
"github.com/azukaar/cosmos-server/src/authorizationserver"
|
||||
"github.com/azukaar/cosmos-server/src/market"
|
||||
"github.com/azukaar/cosmos-server/src/constellation"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
@ -44,5 +45,9 @@ func main() {
|
|||
|
||||
authorizationserver.Init()
|
||||
|
||||
constellation.InitDNS()
|
||||
|
||||
constellation.Init()
|
||||
|
||||
StartServer()
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ type marketGetResult struct {
|
|||
}
|
||||
|
||||
func MarketGet(w http.ResponseWriter, req *http.Request) {
|
||||
if utils.AdminOnly(w, req) != nil {
|
||||
if utils.LoggedInOnly(w, req) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,6 @@ func MarketGet(w http.ResponseWriter, req *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
// return the first 10 results of each market
|
||||
marketGetResult := marketGetResult{
|
||||
All: make(map[string]interface{}),
|
||||
Showcase: []appDefinition{},
|
||||
|
|
|
@ -46,7 +46,7 @@ func joinURLPath(a, b *url.URL) (path, rawpath string) {
|
|||
|
||||
|
||||
// NewProxy takes target host and creates a reverse proxy
|
||||
func NewProxy(targetHost string, AcceptInsecureHTTPSTarget bool, VerboseForwardHeader bool, DisableHeaderHardening bool, CORSOrigin string) (*httputil.ReverseProxy, error) {
|
||||
func NewProxy(targetHost string, AcceptInsecureHTTPSTarget bool, VerboseForwardHeader bool, DisableHeaderHardening bool, CORSOrigin string, route utils.ProxyRouteConfig) (*httputil.ReverseProxy, error) {
|
||||
url, err := url.Parse(targetHost)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -76,16 +76,40 @@ func NewProxy(targetHost string, AcceptInsecureHTTPSTarget bool, VerboseForwardH
|
|||
req.Header.Set("X-Forwarded-Ssl", "on")
|
||||
}
|
||||
|
||||
if CORSOrigin != "" {
|
||||
req.Header.Set("X-Forwarded-Host", url.Host)
|
||||
req.Header.Del("X-Origin-Host")
|
||||
req.Header.Del("X-Forwarded-Host")
|
||||
req.Header.Del("X-Forwarded-For")
|
||||
req.Header.Del("X-Real-Ip")
|
||||
// hide hostname (dangerous)
|
||||
// req.Header.Del("Host")
|
||||
|
||||
hostname := utils.GetMainConfig().HTTPConfig.Hostname
|
||||
if route.Host != "" && route.UseHost {
|
||||
hostname = route.Host
|
||||
}
|
||||
if route.UsePathPrefix {
|
||||
hostname = hostname + route.PathPrefix
|
||||
}
|
||||
|
||||
hostDest := hostname
|
||||
if route.OverwriteHostHeader != "" {
|
||||
hostDest = route.OverwriteHostHeader
|
||||
}
|
||||
|
||||
// hide hostname (dangerous)
|
||||
// req.Header.Set("Host", url.Host)
|
||||
// req.Host = url.Host
|
||||
|
||||
req.Header.Set("Host", hostDest)
|
||||
req.Host = hostDest
|
||||
|
||||
if VerboseForwardHeader {
|
||||
req.Header.Set("X-Origin-Host", url.Host)
|
||||
req.Header.Set("Host", url.Host)
|
||||
req.Header.Set("X-Origin-Host", hostDest)
|
||||
req.Header.Set("X-Forwarded-Host", hostDest)
|
||||
req.Header.Set("X-Forwarded-For", utils.GetClientIP(req))
|
||||
req.Header.Set("X-Real-IP", utils.GetClientIP(req))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if AcceptInsecureHTTPSTarget && url.Scheme == "https" {
|
||||
|
@ -100,8 +124,6 @@ func NewProxy(targetHost string, AcceptInsecureHTTPSTarget bool, VerboseForwardH
|
|||
|
||||
if CORSOrigin != "" {
|
||||
resp.Header.Del("Access-Control-Allow-Origin")
|
||||
resp.Header.Del("Access-Control-Allow-Methods")
|
||||
resp.Header.Del("Access-Control-Allow-Headers")
|
||||
resp.Header.Del("Access-Control-Allow-Credentials")
|
||||
}
|
||||
|
||||
|
@ -126,7 +148,7 @@ func RouteTo(route utils.ProxyRouteConfig) http.Handler {
|
|||
routeType := route.Mode
|
||||
|
||||
if(routeType == "SERVAPP" || routeType == "PROXY") {
|
||||
proxy, err := NewProxy(destination, route.AcceptInsecureHTTPSTarget, route.VerboseForwardHeader, route.DisableHeaderHardening, route.CORSOrigin)
|
||||
proxy, err := NewProxy(destination, route.AcceptInsecureHTTPSTarget, route.VerboseForwardHeader, route.DisableHeaderHardening, route.CORSOrigin, route)
|
||||
if err != nil {
|
||||
utils.Error("Create Route", err)
|
||||
}
|
||||
|
|
|
@ -30,12 +30,12 @@ func tokenMiddleware(enabled bool, adminOnly bool) func(next http.Handler) http.
|
|||
r.Header.Set("x-cosmos-mfa", strconv.Itoa((int)(u.MFAState)))
|
||||
|
||||
ogcookies := r.Header.Get("Cookie")
|
||||
cookieRemoveRegex := regexp.MustCompile(`jwttoken=[^;]*;`)
|
||||
cookieRemoveRegex := regexp.MustCompile(`\s?jwttoken=[^;]*;?\s?`)
|
||||
cookies := cookieRemoveRegex.ReplaceAllString(ogcookies, "")
|
||||
r.Header.Set("Cookie", cookies)
|
||||
|
||||
// Replace the token with a application speicfic one
|
||||
r.Header.Set("x-cosmos-token", "1234567890")
|
||||
//r.Header.Set("x-cosmos-token", "1234567890")
|
||||
|
||||
if enabled && adminOnly {
|
||||
if errT := utils.AdminOnlyWithRedirect(w, r); errT != nil {
|
||||
|
@ -85,6 +85,8 @@ func RouterGen(route utils.ProxyRouteConfig, router *mux.Router, destination htt
|
|||
}
|
||||
}
|
||||
|
||||
destination = utils.Restrictions(route.RestrictToConstellation, route.WhitelistInboundIPs)(destination)
|
||||
|
||||
destination = SmartShieldMiddleware(route.Name, route.SmartShield)(destination)
|
||||
|
||||
originCORS := route.CORSOrigin
|
||||
|
|
|
@ -2,12 +2,9 @@ package user
|
|||
|
||||
import (
|
||||
"net/http"
|
||||
// "io"
|
||||
// "os"
|
||||
"encoding/json"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"time"
|
||||
// "golang.org/x/crypto/bcrypt"
|
||||
|
||||
"github.com/azukaar/cosmos-server/src/utils"
|
||||
)
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"github.com/azukaar/cosmos-server/src/utils"
|
||||
"github.com/golang-jwt/jwt"
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"encoding/json"
|
||||
|
@ -189,13 +190,25 @@ func logOutUser(w http.ResponseWriter, req *http.Request) {
|
|||
HttpOnly: true,
|
||||
}
|
||||
|
||||
clientCookie := http.Cookie{
|
||||
Name: "client-infos",
|
||||
Value: "{}",
|
||||
Expires: time.Now().Add(-time.Hour * 24 * 365),
|
||||
Path: "/",
|
||||
Secure: utils.IsHTTPS,
|
||||
HttpOnly: false,
|
||||
}
|
||||
|
||||
if reqHostNoPort == "localhost" || reqHostNoPort == "0.0.0.0" {
|
||||
cookie.Domain = ""
|
||||
clientCookie.Domain = ""
|
||||
} else {
|
||||
cookie.Domain = "." + reqHostNoPort
|
||||
clientCookie.Domain = "." + reqHostNoPort
|
||||
}
|
||||
|
||||
http.SetCookie(w, &cookie)
|
||||
http.SetCookie(w, &clientCookie)
|
||||
|
||||
// TODO: logout every other device if asked by increasing passwordcycle
|
||||
}
|
||||
|
@ -254,13 +267,24 @@ func SendUserToken(w http.ResponseWriter, req *http.Request, user utils.User, mf
|
|||
HttpOnly: true,
|
||||
}
|
||||
|
||||
clientCookie := http.Cookie{
|
||||
Name: "client-infos",
|
||||
Value: user.Nickname + "," + strconv.Itoa(int(user.Role)),
|
||||
Expires: expiration,
|
||||
Path: "/",
|
||||
Secure: utils.IsHTTPS,
|
||||
HttpOnly: false,
|
||||
}
|
||||
|
||||
utils.Log("UserLogin: Setting cookie for " + reqHostNoPort)
|
||||
|
||||
if reqHostNoPort == "localhost" || reqHostNoPort == "0.0.0.0" {
|
||||
cookie.Domain = ""
|
||||
clientCookie.Domain = ""
|
||||
} else {
|
||||
if utils.IsValidHostname(reqHostNoPort) {
|
||||
cookie.Domain = "." + reqHostNoPort
|
||||
clientCookie.Domain = "." + reqHostNoPort
|
||||
} else {
|
||||
utils.Error("UserLogin: Invalid hostname", nil)
|
||||
utils.HTTPError(w, "User Logging Error", http.StatusInternalServerError, "UL001")
|
||||
|
@ -269,4 +293,5 @@ func SendUserToken(w http.ResponseWriter, req *http.Request, user utils.User, mf
|
|||
}
|
||||
|
||||
http.SetCookie(w, &cookie)
|
||||
http.SetCookie(w, &clientCookie)
|
||||
}
|
|
@ -181,8 +181,7 @@ func DoLetsEncrypt() (string, string) {
|
|||
}
|
||||
|
||||
err = client.Challenge.SetDNS01Provider(provider)
|
||||
}
|
||||
|
||||
} else {
|
||||
err = client.Challenge.SetHTTP01Provider(http01.NewProviderServer("", config.HTTPConfig.HTTPPort))
|
||||
if err != nil {
|
||||
Error("LETSENCRYPT_HTTP01", err)
|
||||
|
@ -196,6 +195,7 @@ func DoLetsEncrypt() (string, string) {
|
|||
LetsEncryptErrors = append(LetsEncryptErrors, err.Error())
|
||||
return "", ""
|
||||
}
|
||||
}
|
||||
|
||||
// New users will need to register
|
||||
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
||||
|
|
|
@ -82,8 +82,6 @@ func CORSHeader(origin string) func(next http.Handler) http.Handler {
|
|||
|
||||
if origin != "" {
|
||||
w.Header().Set("Access-Control-Allow-Origin", origin)
|
||||
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
|
||||
w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
|
||||
w.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||
}
|
||||
|
||||
|
@ -261,3 +259,59 @@ func IsValidHostname(hostname string) bool {
|
|||
|
||||
return false
|
||||
}
|
||||
|
||||
func IPInRange(ipStr, cidrStr string) (bool, error) {
|
||||
_, cidrNet, err := net.ParseCIDR(cidrStr)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("parse CIDR range: %w", err)
|
||||
}
|
||||
|
||||
ip := net.ParseIP(ipStr)
|
||||
if ip == nil {
|
||||
return false, fmt.Errorf("parse IP: invalid IP address")
|
||||
}
|
||||
|
||||
return cidrNet.Contains(ip), nil
|
||||
}
|
||||
|
||||
func Restrictions(RestrictToConstellation bool, WhitelistInboundIPs []string) func(next http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
ip, _, err := net.SplitHostPort(r.RemoteAddr)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
isUsingWhiteList := len(WhitelistInboundIPs) > 0
|
||||
|
||||
isInWhitelist := false
|
||||
isInConstellation := strings.HasPrefix(ip, "192.168.201.") || strings.HasPrefix(ip, "192.168.202.")
|
||||
|
||||
for _, ipRange := range WhitelistInboundIPs {
|
||||
if strings.Contains(ipRange, "/") {
|
||||
if ok, _ := IPInRange(ip, ipRange); ok {
|
||||
isInWhitelist = true
|
||||
}
|
||||
} else {
|
||||
if ip == ipRange {
|
||||
isInWhitelist = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isInConstellationPassing := !RestrictToConstellation || isInConstellation
|
||||
isWhitelistPassing := !isUsingWhiteList || isInWhitelist
|
||||
|
||||
// check if the request is coming from the constellation IP range 192.168.201.0/24
|
||||
if (!isInConstellationPassing && !isWhitelistPassing) {
|
||||
Log("Request from " + ip + " is blocked because of restrictions isInConstellationPassing: " + fmt.Sprintf("%v", isInConstellationPassing) + " and isWhitelistPassing: " + fmt.Sprintf("%v", isWhitelistPassing))
|
||||
http.Error(w, "Access denied", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -90,6 +90,7 @@ type Config struct {
|
|||
MarketConfig MarketConfig
|
||||
HomepageConfig HomepageConfig
|
||||
ThemeConfig ThemeConfig
|
||||
ConstellationConfig ConstellationConfig
|
||||
}
|
||||
|
||||
type HomepageConfig struct {
|
||||
|
@ -180,6 +181,9 @@ type ProxyRouteConfig struct {
|
|||
DisableHeaderHardening bool
|
||||
VerboseForwardHeader bool
|
||||
AddionalFilters []AddionalFiltersConfig
|
||||
RestrictToConstellation bool
|
||||
OverwriteHostHeader string
|
||||
WhitelistInboundIPs []string
|
||||
}
|
||||
|
||||
type EmailConfig struct {
|
||||
|
@ -206,3 +210,114 @@ type MarketSource struct {
|
|||
Name string
|
||||
Url string
|
||||
}
|
||||
|
||||
type ConstellationConfig struct {
|
||||
Enabled bool
|
||||
SlaveMode bool
|
||||
PrivateNode bool
|
||||
DNSDisabled bool
|
||||
DNSPort string
|
||||
DNSFallback string
|
||||
DNSBlockBlacklist bool
|
||||
DNSAdditionalBlocklists []string
|
||||
CustomDNSEntries []ConstellationDNSEntry
|
||||
NebulaConfig NebulaConfig
|
||||
ConstellationHostname string
|
||||
}
|
||||
|
||||
type ConstellationDNSEntry struct {
|
||||
Type string
|
||||
Key string
|
||||
Value string
|
||||
}
|
||||
type ConstellationDevice struct {
|
||||
Nickname string `json:"nickname"`
|
||||
DeviceName string `json:"deviceName"`
|
||||
PublicKey string `json:"publicKey"`
|
||||
IP string `json:"ip"`
|
||||
IsLighthouse bool `json:"isLighthouse"`
|
||||
IsRelay bool `json:"isRelay"`
|
||||
PublicHostname string `json:"publicHostname"`
|
||||
Port string `json:"port"`
|
||||
Blocked bool `json:"blocked"`
|
||||
Fingerprint string `json:"fingerprint"`
|
||||
APIKey string `json:"-"`
|
||||
}
|
||||
|
||||
type NebulaFirewallRule struct {
|
||||
Port string `yaml:"port"`
|
||||
Proto string `yaml:"proto"`
|
||||
Host string `yaml:"host"`
|
||||
Groups []string `yaml:"groups,omitempty"omitempty"`
|
||||
}
|
||||
|
||||
type NebulaConntrackConfig struct {
|
||||
TCPTimeout string `yaml:"tcp_timeout"`
|
||||
UDPTimeout string `yaml:"udp_timeout"`
|
||||
DefaultTimeout string `yaml:"default_timeout"`
|
||||
}
|
||||
|
||||
type NebulaConfig struct {
|
||||
PKI struct {
|
||||
CA string `yaml:"ca"`
|
||||
Cert string `yaml:"cert"`
|
||||
Key string `yaml:"key"`
|
||||
Blocklist []string `yaml:"blocklist"`
|
||||
} `yaml:"pki"`
|
||||
|
||||
StaticHostMap map[string][]string `yaml:"static_host_map"`
|
||||
|
||||
Lighthouse struct {
|
||||
AMLighthouse bool `yaml:"am_lighthouse"`
|
||||
Interval int `yaml:"interval"`
|
||||
Hosts []string `yaml:"hosts"`
|
||||
} `yaml:"lighthouse"`
|
||||
|
||||
Listen struct {
|
||||
Host string `yaml:"host"`
|
||||
Port int `yaml:"port"`
|
||||
} `yaml:"listen"`
|
||||
|
||||
Punchy struct {
|
||||
Punch bool `yaml:"punch"`
|
||||
Respond bool `yaml:"respond"`
|
||||
} `yaml:"punchy"`
|
||||
|
||||
Relay struct {
|
||||
AMRelay bool `yaml:"am_relay"`
|
||||
UseRelays bool `yaml:"use_relays"`
|
||||
Relays []string `yaml:"relays"`
|
||||
} `yaml:"relay"`
|
||||
|
||||
TUN struct {
|
||||
Disabled bool `yaml:"disabled"`
|
||||
Dev string `yaml:"dev"`
|
||||
DropLocalBroadcast bool `yaml:"drop_local_broadcast"`
|
||||
DropMulticast bool `yaml:"drop_multicast"`
|
||||
TxQueue int `yaml:"tx_queue"`
|
||||
MTU int `yaml:"mtu"`
|
||||
Routes []string `yaml:"routes"`
|
||||
UnsafeRoutes []string `yaml:"unsafe_routes"`
|
||||
} `yaml:"tun"`
|
||||
|
||||
Logging struct {
|
||||
Level string `yaml:"level"`
|
||||
Format string `yaml:"format"`
|
||||
} `yaml:"logging"`
|
||||
|
||||
Firewall struct {
|
||||
OutboundAction string `yaml:"outbound_action"`
|
||||
InboundAction string `yaml:"inbound_action"`
|
||||
Conntrack NebulaConntrackConfig `yaml:"conntrack"`
|
||||
Outbound []NebulaFirewallRule `yaml:"outbound"`
|
||||
Inbound []NebulaFirewallRule `yaml:"inbound"`
|
||||
} `yaml:"firewall"`
|
||||
}
|
||||
|
||||
type Device struct {
|
||||
DeviceName string `json:"deviceName",validate:"required,min=3,max=32,alphanum"`
|
||||
Nickname string `json:"nickname",validate:"required,min=3,max=32,alphanum"`
|
||||
PublicKey string `json:"publicKey",omitempty`
|
||||
PrivateKey string `json:"privateKey",omitempty`
|
||||
IP string `json:"ip",validate:"required,ipv4"`
|
||||
}
|
||||
|
|
|
@ -37,6 +37,8 @@ var ReBootstrapContainer func(string) error
|
|||
|
||||
var LetsEncryptErrors = []string{}
|
||||
|
||||
var CONFIGFOLDER = "/config/"
|
||||
|
||||
var DefaultConfig = Config{
|
||||
LoggingLevel: "INFO",
|
||||
NewInstall: true,
|
||||
|
@ -60,6 +62,17 @@ var DefaultConfig = Config{
|
|||
Sources: []MarketSource{
|
||||
},
|
||||
},
|
||||
ConstellationConfig: ConstellationConfig{
|
||||
Enabled: false,
|
||||
DNSDisabled: false,
|
||||
DNSFallback: "8.8.8.8:53",
|
||||
DNSAdditionalBlocklists: []string{
|
||||
"https://s3.amazonaws.com/lists.disconnect.me/simple_tracking.txt",
|
||||
"https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt",
|
||||
"https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts",
|
||||
"https://raw.githubusercontent.com/StevenBlack/hosts/master/alternates/fakenews-only/hosts",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func FileExists(path string) bool {
|
||||
|
@ -193,10 +206,23 @@ func LoadBaseMainConfig(config Config) {
|
|||
if os.Getenv("COSMOS_SERVER_COUNTRY") != "" {
|
||||
MainConfig.ServerCountry = os.Getenv("COSMOS_SERVER_COUNTRY")
|
||||
}
|
||||
if os.Getenv("COSMOS_CONFIG_FOLDER") != "" {
|
||||
Log("Overwriting config folder with " + os.Getenv("COSMOS_CONFIG_FOLDER"))
|
||||
CONFIGFOLDER = os.Getenv("COSMOS_CONFIG_FOLDER")
|
||||
}
|
||||
|
||||
if MainConfig.DockerConfig.DefaultDataPath == "" {
|
||||
MainConfig.DockerConfig.DefaultDataPath = "/usr"
|
||||
}
|
||||
|
||||
if MainConfig.ConstellationConfig.ConstellationHostname == "" {
|
||||
// if hostname is a domain add vpn. suffix otherwise use hostname
|
||||
if IsDomain(MainConfig.HTTPConfig.Hostname) {
|
||||
MainConfig.ConstellationConfig.ConstellationHostname = "vpn." + MainConfig.HTTPConfig.Hostname
|
||||
} else {
|
||||
MainConfig.ConstellationConfig.ConstellationHostname = MainConfig.HTTPConfig.Hostname
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func GetMainConfig() Config {
|
||||
|
@ -219,7 +245,7 @@ func GetConfigFileName() string {
|
|||
configFile := os.Getenv("CONFIG_FILE")
|
||||
|
||||
if configFile == "" {
|
||||
configFile = "/config/cosmos.config.json"
|
||||
configFile = CONFIGFOLDER + "cosmos.config.json"
|
||||
}
|
||||
|
||||
return configFile
|
||||
|
@ -539,6 +565,21 @@ func GetNetworkUsage() NetworkStatus {
|
|||
return NetworkStatus{}
|
||||
}
|
||||
|
||||
func DownloadFile(url string) (string, error) {
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(body), nil
|
||||
}
|
||||
|
||||
func GetClientIP(req *http.Request) string {
|
||||
/*ip := req.Header.Get("X-Forwarded-For")
|
||||
if ip == "" {
|
||||
|
@ -546,3 +587,11 @@ func GetClientIP(req *http.Request) string {
|
|||
}*/
|
||||
return req.RemoteAddr
|
||||
}
|
||||
|
||||
func IsDomain(domain string) bool {
|
||||
// contains . and at least a letter and no special characters invalid in a domain
|
||||
if strings.Contains(domain, ".") && strings.ContainsAny(domain, "abcdefghijklmnopqrstuvwxyz") && !strings.ContainsAny(domain, " !@#$%^&*()+=[]{}\\|;:'\",/<>?") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
Loading…
Reference in a new issue