Merge branch 'unstable'

This commit is contained in:
Yann Stepienik 2023-10-07 15:54:47 +01:00
commit f796c521ef
66 changed files with 5248 additions and 1323 deletions

View file

@ -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
View file

@ -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

66
LICENCE
View file

@ -1,31 +1,57 @@
“Commons Clause” License Condition v1.0
The Software is provided to you by the Licensor under the
License, as defined below, subject to the following condition.
Without limiting other conditions in the License, the grant
of rights under the License will not include, and the License
does not grant to you, the right to Sell the Software.
For purposes of the foregoing, “Sell” means practicing any or
all of the rights granted to you under the License to provide
to third parties, for a fee or other consideration (including
without limitation fees for hosting or consulting/ support
services related to the Software), a product or service whose
value derives, entirely or substantially, from the functionality
of the Software. 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
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
License, as defined below, subject to the following condition.
Without limiting other conditions in the License, the grant
of rights under the License will not include, and the License
does not grant to you, the right to Sell the Software.
For purposes of the foregoing, “Sell” means practicing any or
all of the rights granted to you under the License to provide
to third parties, for a fee or other consideration (including
without limitation fees for hosting or consulting/ support
services related to the Software), a product or service whose
value derives, entirely or substantially, from the functionality
of the Software. Any license notice or attribution required by
the License must also include this Commons Clause License
Condition notice.
---------------------------------------------------------------------
"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

View file

@ -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

View file

@ -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
View file

@ -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 individuals 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 individuals 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.

View 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,
};

View 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,
};

View file

@ -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,

View 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>
);
}

View file

@ -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,

View file

@ -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 doesnt 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"
})
});
});
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

View 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;

View 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;

View file

@ -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>

View file

@ -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 {

View file

@ -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}

View file

@ -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',

View file

@ -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 ||================================ //

View file

@ -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

View file

@ -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)',

View file

@ -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>
)}

View file

@ -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>
}

View 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;

View 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>}
</>
};

View 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;

View 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>}
</>
};

View file

@ -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 />

View file

@ -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={{

View file

@ -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
View 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
};

View file

@ -1,3 +1,5 @@
import { Button } from "@mui/material";
export const randomString = (length) => {
let text = "";
const possible =

View file

@ -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

View file

@ -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
View file

@ -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
View file

@ -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
View file

@ -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",

View file

@ -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"
},

View file

@ -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.

View file

@ -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

View file

@ -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)

View file

@ -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
View 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)
}
})()
}
}

View 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
}
}

View 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
// }
// }

View 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
}
}

View 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
}
}

View 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,
})
}

View 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
}
}

View 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
}
}

View 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
View 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
}

View 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",
},
},
},
}
}

View file

@ -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)

View file

@ -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()
}

View file

@ -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{},

View file

@ -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)
}

View file

@ -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

View file

@ -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"
)

View file

@ -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)
}

View file

@ -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})

View file

@ -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)
})
}
}

View file

@ -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"`
}

View file

@ -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
}