From 89e7dbbed5df83a5e3912d617fe0c660252edff4 Mon Sep 17 00:00:00 2001 From: Vittorio Palmisano Date: Thu, 25 Feb 2021 13:05:27 +0100 Subject: [PATCH] adding docker compose configuration for edumeet / prometheus / grafana --- compose/.gitignore | 1 + compose/README.md | 15 + compose/config/edumeet-app-config.js | 275 ++++++++++ compose/config/edumeet-server-config.js | 505 ++++++++++++++++++ .../config/grafana-prometheus-datasource.yml | 28 + compose/config/prometheus.yml | 26 + compose/docker-compose.yml | 97 ++++ compose/edumeet/Dockerfile | 8 + 8 files changed, 955 insertions(+) create mode 100644 compose/.gitignore create mode 100644 compose/README.md create mode 100644 compose/config/edumeet-app-config.js create mode 100644 compose/config/edumeet-server-config.js create mode 100644 compose/config/grafana-prometheus-datasource.yml create mode 100644 compose/config/prometheus.yml create mode 100644 compose/docker-compose.yml create mode 100644 compose/edumeet/Dockerfile diff --git a/compose/.gitignore b/compose/.gitignore new file mode 100644 index 00000000..1269488f --- /dev/null +++ b/compose/.gitignore @@ -0,0 +1 @@ +data diff --git a/compose/README.md b/compose/README.md new file mode 100644 index 00000000..4994a17e --- /dev/null +++ b/compose/README.md @@ -0,0 +1,15 @@ +# Running the development environment + +Starting: + +```sh +docker-compose up --build -d + +docker-compose logs -f edumeet +``` + +Accessing endpoints: + +- Edumeet: https://127.0.0.1:4443/ +- Prometheus: http://127.0.0.1:9090/ +- Grafana: http://127.0.0.1:9091/ diff --git a/compose/config/edumeet-app-config.js b/compose/config/edumeet-app-config.js new file mode 100644 index 00000000..9d3a9001 --- /dev/null +++ b/compose/config/edumeet-app-config.js @@ -0,0 +1,275 @@ +// eslint-disable-next-line +var config = +{ + loginEnabled : false, + developmentPort : 3443, + productionPort : 3443, + + /** + * Supported browsers version + * in bowser satisfy format. + * See more: + * https://www.npmjs.com/package/bowser#filtering-browsers + * Otherwise you got a unsupported browser page + */ + supportedBrowsers : + { + 'windows' : { + 'internet explorer' : '>12', + 'microsoft edge' : '>18' + }, + 'safari' : '>12', + 'firefox' : '>=60', + 'chrome' : '>=74', + 'chromium' : '>=74', + 'opera' : '>=62', + 'samsung internet for android' : '>=11.1.1.52' + }, + + /** + * Resolutions: + * + * low ~ 320x240 + * medium ~ 640x480 + * high ~ 1280x720 + * veryhigh ~ 1920x1080 + * ultra ~ 3840x2560 + * + **/ + + /** + * Frame rates: + * + * 1, 5, 10, 15, 20, 25, 30 + * + **/ + // The aspect ratio of the videos as shown on + // the screen. This is changeable in client settings. + // This value must match one of the defined values in + // viewAspectRatios EXACTLY (e.g. 1.333) + viewAspectRatio : 1.777, + // These are the selectable aspect ratios in the settings + viewAspectRatios : [ { + value : 1.333, // 4 / 3 + label : '4 : 3' + }, { + value : 1.777, // 16 / 9 + label : '16 : 9' + } ], + // The aspect ratio of the video from the camera + // this is not changeable in settings, only config + videoAspectRatio : 1.777, + defaultResolution : 'high', + defaultFrameRate : 25, + defaultScreenResolution : 'veryhigh', + defaultScreenSharingFrameRate : 5, + // Enable or disable simulcast for webcam video + simulcast : true, + // Enable or disable simulcast for screen sharing video + simulcastSharing : false, + // Simulcast encoding layers and levels + simulcastEncodings : + [ + /* { scaleResolutionDownBy: 4 }, + { scaleResolutionDownBy: 2 }, + { scaleResolutionDownBy: 1 } */ + { maxBitRate: 50000 }, + { maxBitRate: 1000000 }, + { maxBitRate: 4800000 } + ], + // The adaptive spatial layer selection scaling factor (in the range [0.5, 1.0]) + // example: + // with level width=640px, the minimum width required to trigger the + // level change will be: 640 * 0.75 = 480px + adaptiveScalingFactor: 0.75, + + /** + * Alternative simulcast setting: + * [ + * { maxBitRate: 50000 }, + * { maxBitRate: 1000000 }, + * { maxBitRate: 4800000 } + *], + **/ + + /** + * White listing browsers that support audio output device selection. + * It is not yet fully implemented in Firefox. + * See: https://bugzilla.mozilla.org/show_bug.cgi?id=1498512 + */ + audioOutputSupportedBrowsers : + [ + 'chrome', + 'opera' + ], + // Socket.io request timeout + requestTimeout : 20000, + requestRetries : 3, + transportOptions : + { + tcp : true + }, + // defaults for audio setting on new clients / can be customized and overruled from client side + defaultAudio: + { + autoGainControl : false, // default : false + echoCancellation : true, // default : true + noiseSuppression : true, // default : true + voiceActivatedUnmute : false, // default : false / Automatically unmute speaking above noisThereshold + noiseThreshold : -60 // default -60 / This is only for voiceActivatedUnmute and audio-indicator + }, + // Audio options for now only centrally from config file: + centralAudioOptions: + { + sampleRate : 96000, // default : 96khz / will not eat that much bandwith thanks to opus + channelCount : 1, // default : 1 / usually mics are mono so this saves bandwidth + volume : 1.0, // default : 1.0 + sampleSize : 16, // default : 16 + opusStereo : false, // default : false / usually mics are mono so this saves bandwidth + opusDtx : true, // default : true / will save bandwidth + opusFec : true, // default : true / forward error correction + opusPtime : '20', // default : 20 / minimum packet time (3, 5, 10, 20, 40, 60, 120) + opusMaxPlaybackRate : 96000 + }, + /** + * Set max number participants in one room that join + * unmuted. Next participant will join automatically muted + * Default value is 4 + * + * Set it to 0 to auto mute all, + * Set it to negative (-1) to never automatically auto mute + * but use it with caution + * full mesh audio strongly decrease room capacity! + */ + autoMuteThreshold : 4, + background : 'images/background.jpg', + defaultLayout : 'democratic', // democratic, filmstrip + // If true, will show media control buttons in separate + // control bar, not in the ME container. + buttonControlBar : false, + // If false, will push videos away to make room for side + // drawer. If true, will overlay side drawer over videos + drawerOverlayed : true, + // Position of notifications + notificationPosition : 'right', + // Timeout for autohiding topbar and button control bar + hideTimeout : 3000, + // max number of participant that will be visible in + // as speaker + lastN : 4, + mobileLastN : 1, + // Highest number of lastN the user can select manually in + // userinteface + maxLastN : 25, + // If truthy, users can NOT change number of speakers visible + lockLastN : false, + // Show logo if "logo" is not null, else show title + // Set logo file name using logo.* pattern like "logo.png" to not track it by git + logo : 'images/logo.edumeet.svg', + title : 'edumeet', + // Service & Support URL + // if not set then not displayed on the about modals + supportUrl : 'https://support.example.com', + // Privacy and dataprotection URL or path + // by default privacy/privacy.html + // that is a placeholder for your policies + // + // but an external url could be also used here + privacyUrl : 'privacy/privacy.html', + theme : + { + palette : + { + primary : + { + main : '#313131' + } + }, + overrides : + { + MuiAppBar : + { + colorPrimary : + { + backgroundColor : '#313131' + } + }, + MuiButton : + { + containedPrimary : + { + backgroundColor : '#5F9B2D', + '&:hover' : + { + backgroundColor : '#5F9B2D' + } + }, + containedSecondary : + { + backgroundColor : '#f50057', + '&:hover' : + { + backgroundColor : '#f50057' + } + } + + }, + MuIconButton : + { + colorPrimary : + { + backgroundColor : '#5F9B2D', + '&:hover' : + { + backgroundColor : '#5F9B2D' + } + }, + colorSecondary : + { + backgroundColor : '#f50057', + '&:hover' : + { + backgroundColor : '#f50057' + } + } + + }, + + MuiFab : + { + primary : + { + backgroundColor : '#5F9B2D', + '&:hover' : + { + backgroundColor : '#5F9B2D' + } + }, + secondary : + { + backgroundColor : '#f50057', + '&:hover' : + { + backgroundColor : '#f50057' + } + } + + }, + MuiBadge : + { + colorPrimary : + { + backgroundColor : '#5F9B2D', + '&:hover' : + { + backgroundColor : '#518029' + } + } + } + }, + typography : + { + useNextVariants : true + } + } +}; diff --git a/compose/config/edumeet-server-config.js b/compose/config/edumeet-server-config.js new file mode 100644 index 00000000..cb41698a --- /dev/null +++ b/compose/config/edumeet-server-config.js @@ -0,0 +1,505 @@ +const os = require('os'); +// const fs = require('fs'); + +const userRoles = require('../userRoles'); + +const { + BYPASS_ROOM_LOCK, + BYPASS_LOBBY +} = require('../access'); + +const { + CHANGE_ROOM_LOCK, + PROMOTE_PEER, + MODIFY_ROLE, + SEND_CHAT, + MODERATE_CHAT, + SHARE_AUDIO, + SHARE_VIDEO, + SHARE_SCREEN, + EXTRA_VIDEO, + SHARE_FILE, + MODERATE_FILES, + MODERATE_ROOM +} = require('../permissions'); + +// const AwaitQueue = require('awaitqueue'); +// const axios = require('axios'); + +// To gather ip address only on interface like eth0, ens0p3 +const ifaceWhiteListRegex = /^(eth.*)|(ens.*)|(tun.*)/ + +function getListenIps() { + let listenIP = []; + const ifaces = os.networkInterfaces(); + Object.keys(ifaces).forEach(function (ifname) { + if (ifname.match(ifaceWhiteListRegex)) { + ifaces[ifname].forEach(function (iface) { + if ( + (iface.family !== "IPv4" && + (iface.family !== "IPv6" || iface.scopeid !== 0)) || + iface.internal !== false + ) { + // skip over internal (i.e. 127.0.0.1) and non-ipv4 or ipv6 non global addresses + return; + } + listenIP.push({ ip: iface.address, announcedIp: iface.address }); + }); + } + }); + console.log('Using listenips:', listenIP); + return listenIP; +} + +module.exports = +{ + + // Auth conf + /* + auth : + { + // Always enabled if configured + lti : + { + consumerKey : 'key', + consumerSecret : 'secret' + }, + + // Auth strategy to use (default oidc) + strategy : 'oidc', + oidc : + { + // The issuer URL for OpenID Connect discovery + // The OpenID Provider Configuration Document + // could be discovered on: + // issuerURL + '/.well-known/openid-configuration' + + // e.g. google OIDC config + // Follow this guide to get credential: + // https://developers.google.com/identity/protocols/oauth2/openid-connect + // use this issuerURL + // issuerURL : 'https://accounts.google.com/', + + issuerURL : 'https://example.com', + clientOptions : + { + client_id : '', + client_secret : '', + scope : 'openid email profile', + // where client.example.com is your edumeet server + redirect_uri : 'https://client.example.com/auth/callback' + } + + }, + saml : + { + // where edumeet.example.com is your edumeet server + callbackUrl : 'https://edumeet.example.com/auth/callback', + issuer : 'https://edumeet.example.com', + entryPoint : 'https://openidp.feide.no/simplesaml/saml2/idp/SSOService.php', + privateCert : fs.readFileSync('config/saml_privkey.pem', 'utf-8'), + signingCert : fs.readFileSync('config/saml_cert.pem', 'utf-8'), + decryptionPvk : fs.readFileSync('config/saml_privkey.pem', 'utf-8'), + decryptionCert : fs.readFileSync('config/saml_cert.pem', 'utf-8'), + // Federation cert + cert : fs.readFileSync('config/federation_cert.pem', 'utf-8') + }, + + // to create password hash use: node server/utils/password_encode.js cleartextpassword + local : + { + users : [ + { + id : 1, + username : 'alice', + passwordHash : '$2b$10$PAXXw.6cL3zJLd7ZX.AnL.sFg2nxjQPDmMmGSOQYIJSa0TrZ9azG6', + displayName : 'Alice', + emails : [ { value: 'alice@atlanta.com' } ] + }, + { + id : 2, + username : 'bob', + passwordHash : '$2b$10$BzAkXcZ54JxhHTqCQcFn8.H6klY/G48t4jDBeTE2d2lZJk/.tvv0G', + displayName : 'Bob', + emails : [ { value: 'bob@biloxi.com' } ] + } + ] + } + }, + */ + // URI and key for requesting geoip-based TURN server closest to the client + //turnAPIKey : 'examplekey', + //turnAPIURI : 'https://example.com/api/turn', + //turnAPIparams : { + // 'uri_schema' : 'turn', + // 'transport' : 'tcp', + // 'ip_ver' : 'ipv4', + // 'servercount' : '2' + //}, + //turnAPITimeout : 2 * 1000, + // Backup turnservers if REST fails or is not configured + //backupTurnServers : [ + // { + // urls : [ + // 'turn:turn.example.com:443?transport=tcp' + // ], + // username : 'example', + // credential : 'example' + // } + //], + // bittorrent tracker + fileTracker : 'wss://tracker.lab.vvc.niif.hu:443', + // redis server options + redisOptions : { + host: 'redis', + port: 6379 + }, + // session cookie secret + cookieSecret : 'T0P-S3cR3t_cook!e', + cookieName : 'edumeet.sid', + // if you use encrypted private key the set the passphrase + tls : + { + cert : `${__dirname}/../certs/mediasoup-demo.localhost.cert.pem`, + // passphrase: 'key_password' + key : `${__dirname}/../certs/mediasoup-demo.localhost.key.pem` + }, + // listening Host or IP + // If omitted listens on every IP. ("0.0.0.0" and "::") + // listeningHost: 'localhost', + // Listening port for https server. + listeningPort : 3443, + // Any http request is redirected to https. + // Listening port for http server. + listeningRedirectPort : 8080, + // Listens only on http, only on listeningPort + // listeningRedirectPort disabled + // use case: loadbalancer backend + httpOnly : false, + // WebServer/Express trust proxy config for httpOnly mode + // You can find more info: + // - https://expressjs.com/en/guide/behind-proxies.html + // - https://www.npmjs.com/package/proxy-addr + // use case: loadbalancer backend + trustProxy : '', + // This logger class will have the log function + // called every time there is a room created or destroyed, + // or peer created or destroyed. This would then be able + // to log to a file or external service. + /* StatusLogger : class + { + constructor() + { + this._queue = new AwaitQueue(); + } + + // rooms: rooms object + // peers: peers object + // eslint-disable-next-line no-unused-vars + async log({ rooms, peers }) + { + this._queue.push(async () => + { + // Do your logging in here, use queue to keep correct order + + // eslint-disable-next-line no-console + console.log('Number of rooms: ', rooms.size); + // eslint-disable-next-line no-console + console.log('Number of peers: ', peers.size); + }) + .catch((error) => + { + // eslint-disable-next-line no-console + console.log('error in log', error); + }); + } + }, */ + // This function will be called on successful login through oidc. + // Use this function to map your oidc userinfo to the Peer object. + // The roomId is equal to the room name. + // See examples below. + // Examples: + /* + // All authenicated users will be MODERATOR and AUTHENTICATED + userMapping : async ({ peer, room, roomId, userinfo }) => + { + peer.addRole(userRoles.MODERATOR); + peer.addRole(userRoles.AUTHENTICATED); + }, + // All authenicated users will be AUTHENTICATED, + // and those with the moderator role set in the userinfo + // will also be MODERATOR + userMapping : async ({ peer, room, roomId, userinfo }) => + { + if ( + Array.isArray(userinfo.meet_roles) && + userinfo.meet_roles.includes('moderator') + ) + { + peer.addRole(userRoles.MODERATOR); + } + + if ( + Array.isArray(userinfo.meet_roles) && + userinfo.meet_roles.includes('meetingadmin') + ) + { + peer.addRole(userRoles.ADMIN); + } + + peer.addRole(userRoles.AUTHENTICATED); + }, + // First authenticated user will be moderator, + // all others will be AUTHENTICATED + userMapping : async ({ peer, room, roomId, userinfo }) => + { + if (room) + { + const peers = room.getJoinedPeers(); + + if (peers.some((_peer) => _peer.authenticated)) + peer.addRole(userRoles.AUTHENTICATED); + else + { + peer.addRole(userRoles.MODERATOR); + peer.addRole(userRoles.AUTHENTICATED); + } + } + }, + // All authenicated users will be AUTHENTICATED, + // and those with email ending with @example.com + // will also be MODERATOR + userMapping : async ({ peer, room, roomId, userinfo }) => + { + if (userinfo.email && userinfo.email.endsWith('@example.com')) + { + peer.addRole(userRoles.MODERATOR); + } + + peer.addRole(userRoles.AUTHENTICATED); + }, + // All authenicated users will be AUTHENTICATED, + // and those with email ending with @example.com + // will also be MODERATOR + userMapping : async ({ peer, room, roomId, userinfo }) => + { + if (userinfo.email && userinfo.email.endsWith('@example.com')) + { + peer.addRole(userRoles.MODERATOR); + } + + peer.addRole(userRoles.AUTHENTICATED); + }, + */ + // eslint-disable-next-line no-unused-vars + userMapping : async ({ peer, room, roomId, userinfo }) => + { + if (userinfo.picture != null) + { + if (!userinfo.picture.match(/^http/g)) + { + peer.picture = `data:image/jpeg;base64, ${userinfo.picture}`; + } + else + { + peer.picture = userinfo.picture; + } + } + if (userinfo['urn:oid:0.9.2342.19200300.100.1.60'] != null) + { + peer.picture = `data:image/jpeg;base64, ${userinfo['urn:oid:0.9.2342.19200300.100.1.60']}`; + } + + if (userinfo.nickname != null) + { + peer.displayName = userinfo.nickname; + } + + if (userinfo.name != null) + { + peer.displayName = userinfo.name; + } + + if (userinfo.displayName != null) + { + peer.displayName = userinfo.displayName; + } + + if (userinfo['urn:oid:2.16.840.1.113730.3.1.241'] != null) + { + peer.displayName = userinfo['urn:oid:2.16.840.1.113730.3.1.241']; + } + + if (userinfo.email != null) + { + peer.email = userinfo.email; + } + }, + // All users have the role "NORMAL" by default. Other roles need to be + // added in the "userMapping" function. The following accesses and + // permissions are arrays of roles. Roles can be changed in userRoles.js + // + // Example: + // [ userRoles.MODERATOR, userRoles.AUTHENTICATED ] + accessFromRoles : { + // The role(s) will gain access to the room + // even if it is locked (!) + [BYPASS_ROOM_LOCK] : [ userRoles.ADMIN ], + // The role(s) will gain access to the room without + // going into the lobby. If you want to restrict access to your + // server to only directly allow authenticated users, you could + // add the userRoles.AUTHENTICATED to the user in the userMapping + // function, and change to BYPASS_LOBBY : [ userRoles.AUTHENTICATED ] + [BYPASS_LOBBY] : [ userRoles.NORMAL ] + }, + permissionsFromRoles : { + // The role(s) have permission to lock/unlock a room + [CHANGE_ROOM_LOCK] : [ userRoles.MODERATOR ], + // The role(s) have permission to promote a peer from the lobby + [PROMOTE_PEER] : [ userRoles.NORMAL ], + // The role(s) have permission to give/remove other peers roles + [MODIFY_ROLE] : [ userRoles.NORMAL ], + // The role(s) have permission to send chat messages + [SEND_CHAT] : [ userRoles.NORMAL ], + // The role(s) have permission to moderate chat + [MODERATE_CHAT] : [ userRoles.MODERATOR ], + // The role(s) have permission to share audio + [SHARE_AUDIO] : [ userRoles.NORMAL ], + // The role(s) have permission to share video + [SHARE_VIDEO] : [ userRoles.NORMAL ], + // The role(s) have permission to share screen + [SHARE_SCREEN] : [ userRoles.NORMAL ], + // The role(s) have permission to produce extra video + [EXTRA_VIDEO] : [ userRoles.NORMAL ], + // The role(s) have permission to share files + [SHARE_FILE] : [ userRoles.NORMAL ], + // The role(s) have permission to moderate files + [MODERATE_FILES] : [ userRoles.MODERATOR ], + // The role(s) have permission to moderate room (e.g. kick user) + [MODERATE_ROOM] : [ userRoles.MODERATOR ] + }, + // Array of permissions. If no peer with the permission in question + // is in the room, all peers are permitted to do the action. The peers + // that are allowed because of this rule will not be able to do this + // action as soon as a peer with the permission joins. In this example + // everyone will be able to lock/unlock room until a MODERATOR joins. + allowWhenRoleMissing : [ CHANGE_ROOM_LOCK ], + // When truthy, the room will be open to all users when as long as there + // are allready users in the room + activateOnHostJoin : true, + // When set, maxUsersPerRoom defines how many users can join + // a single room. If not set, there is no limit. + // maxUsersPerRoom : 20, + // Room size before spreading to new router + routerScaleSize : 40, + // Socket timout value + requestTimeout : 20000, + // Socket retries when timeout + requestRetries : 3, + // Mediasoup settings + mediasoup : + { + numWorkers : Object.keys(os.cpus()).length, + // mediasoup Worker settings. + worker : + { + logLevel : 'warn', + logTags : + [ + 'info', + 'ice', + 'dtls', + 'rtp', + 'srtp', + 'rtcp' + ], + rtcMinPort : 40000, + rtcMaxPort : 49999 + }, + // mediasoup Router settings. + router : + { + // Router media codecs. + mediaCodecs : + [ + { + kind : 'audio', + mimeType : 'audio/opus', + clockRate : 48000, + channels : 2 + }, + { + kind : 'video', + mimeType : 'video/VP8', + clockRate : 90000, + parameters : + { + 'x-google-start-bitrate' : 1000 + } + }, + { + kind : 'video', + mimeType : 'video/VP9', + clockRate : 90000, + parameters : + { + 'profile-id' : 2, + 'x-google-start-bitrate' : 1000 + } + }, + { + kind : 'video', + mimeType : 'video/h264', + clockRate : 90000, + parameters : + { + 'packetization-mode' : 1, + 'profile-level-id' : '4d0032', + 'level-asymmetry-allowed' : 1, + 'x-google-start-bitrate' : 1000 + } + }, + { + kind : 'video', + mimeType : 'video/h264', + clockRate : 90000, + parameters : + { + 'packetization-mode' : 1, + 'profile-level-id' : '42e01f', + 'level-asymmetry-allowed' : 1, + 'x-google-start-bitrate' : 1000 + } + } + ] + }, + // mediasoup WebRtcTransport settings. + webRtcTransport : + { + listenIps : getListenIps(), + /*[ + // change 192.0.2.1 IPv4 to your server's IPv4 address!! + //{ ip: '192.0.2.1', announcedIp: null } + // Can have multiple listening interfaces + // change 2001:DB8::1 IPv6 to your server's IPv6 address!! + // { ip: '2001:DB8::1', announcedIp: null } + ],*/ + initialAvailableOutgoingBitrate : 1000000, + minimumAvailableOutgoingBitrate : 600000, + // Additional options that are not part of WebRtcTransportOptions. + maxIncomingBitrate : 1500000 + } + } + + + , + // Prometheus exporter + prometheus : { + deidentify : false, // deidentify IP addresses + // listen : 'localhost', // exporter listens on this address + numeric : false, // show numeric IP addresses + port : 8889, // allocated port + quiet : false // include fewer labels + } + +}; diff --git a/compose/config/grafana-prometheus-datasource.yml b/compose/config/grafana-prometheus-datasource.yml new file mode 100644 index 00000000..ae1b4ab8 --- /dev/null +++ b/compose/config/grafana-prometheus-datasource.yml @@ -0,0 +1,28 @@ +apiVersion: 1 + +deleteDatasources: +- name: Prometheus + orgId: 1 + +datasources: +- name: Prometheus + type: prometheus + access: proxy + url: http://prometheus:9090 + password: + user: + database: prometheus + basicAuth: false + basicAuthUser: + basicAuthPassword: + withCredentials: + isDefault: true + jsonData: + tlsAuth: false + tlsAuthWithCACert: false + secureJsonData: + tlsCACert: "" + tlsClientCert: "" + tlsClientKey: "" + version: 1 + editable: true \ No newline at end of file diff --git a/compose/config/prometheus.yml b/compose/config/prometheus.yml new file mode 100644 index 00000000..22458b89 --- /dev/null +++ b/compose/config/prometheus.yml @@ -0,0 +1,26 @@ +# global config +global: + scrape_interval: 120s # By default, scrape targets every 15 seconds. + evaluation_interval: 120s # By default, scrape targets every 15 seconds. + # scrape_timeout is set to the global default (10s). + # Attach these labels to any time series or alerts when communicating with + # external systems (federation, remote storage, Alertmanager). + external_labels: + monitor: 'edumeet' + +# Load and evaluate rules in this file every 'evaluation_interval' seconds. +rule_files: +# - "alert.rules" +# - "first.rules" +# - "second.rules" + +# A scrape configuration containing exactly one endpoint to scrape: +# Here it's Prometheus itself. +scrape_configs: +# The job name is added as a label `job=` to any timeseries scraped from this config. +- job_name: 'prometheus' + scrape_interval: 15s + # metrics_path defaults to '/metrics' + # scheme defaults to 'http'. + static_configs: + - targets: ['localhost:9090','node-exporter:9100','edumeet:8889'] \ No newline at end of file diff --git a/compose/docker-compose.yml b/compose/docker-compose.yml new file mode 100644 index 00000000..5e12dfc6 --- /dev/null +++ b/compose/docker-compose.yml @@ -0,0 +1,97 @@ + +networks: + edumeet: + driver: bridge + ipam: + driver: default + config: + - subnet: 172.22.0.0/24 + +services: + + edumeet: + build: ./edumeet + container_name: edumeet + restart: unless-stopped + user: "${UID}:${GID}" + volumes: + - ${PWD}/..:/edumeet + - ${PWD}/config/edumeet-server-config.js:/edumeet/server/config/config.js:ro + - ${PWD}/config/edumeet-app-config.js:/edumeet/app/public/config/config.js:ro + environment: + - DEBUG="edumeet*,mediasoup*" + network_mode: "host" + extra_hosts: + redis: 172.22.0.2 + depends_on: + - redis + + redis: + image: redis + container_name: edumeet_redis + restart: unless-stopped + expose: + - 6379 + networks: + edumeet: + ipv4_address: 172.22.0.2 + + prometheus: + image: prom/prometheus:latest + user: root + container_name: edumeet_prometheus + restart: unless-stopped + volumes: + - ./config/prometheus.yml:/etc/prometheus/prometheus.yml + - ./data/prometheus:/prometheus + command: + - '--config.file=/etc/prometheus/prometheus.yml' + - '--storage.tsdb.path=/prometheus' + expose: + - 9090 + ports: + - 9090:9090 + links: + #- cadvisor:cadvisor + - node-exporter:node-exporter + - edumeet:edumeet + extra_hosts: + edumeet: 172.22.0.1 + + node-exporter: + image: prom/node-exporter:latest + container_name: edumeet_node_exporter + restart: unless-stopped + expose: + - 9100 + + #cadvisor: + # image: google/cadvisor:latest + # container_name: edumeet_cadvisor + # restart: unless-stopped + # volumes: + # - /:/rootfs:ro + # - /var/run:/var/run:rw + # - /sys:/sys:ro + # - /var/lib/docker/:/var/lib/docker:ro + # expose: + # - 8080 + + grafana: + image: grafana/grafana:latest + user: root + container_name: edumeet_grafana + restart: unless-stopped + links: + - prometheus:prometheus + expose: + - 9091 + ports: + - 9091:3000 + volumes: + - ./config/grafana-prometheus-datasource.yml:/etc/grafana/provisioning/datasources/prometheus.yml + - ./data/grafana:/var/lib/grafana + environment: + - GF_SECURITY_ADMIN_USER=admin + - GF_SECURITY_ADMIN_PASSWORD=admin + - GF_USERS_ALLOW_SIGN_UP=false diff --git a/compose/edumeet/Dockerfile b/compose/edumeet/Dockerfile new file mode 100644 index 00000000..04c7ef04 --- /dev/null +++ b/compose/edumeet/Dockerfile @@ -0,0 +1,8 @@ +FROM node:14-slim +RUN apt-get update && \ + apt-get install -y git build-essential python && \ + apt-get clean +WORKDIR /edumeet +RUN npm install -g nodemon && \ + npm install -g concurrently +CMD concurrently --names "server,app" "cd server && yarn && nodemon server.js" "cd app && yarn && yarn start" \ No newline at end of file