Blog
Veröffentlicht vor 2 Jahren
Im letzten Teil haben wir die die Infrastruktur unseres Servers definiert, die wir für den sicheren Betrieb von code-server benötigen.
Nun kommen wir im dritten Teil endlich zum Herzstück des gesamten Setups: code-server.
Für code-server wie auch für VSCode gibt es sehr viele Erweiterungen und Themes, so dass wahrscheinich jeder Entwickler sein eigenes ganz individuelles Setup hat, mit dem er sich am wohlsten fühlt. Ich zeige hier, wie ich mein Setup eingerichtet habe.
Für die Kommandozeile nutze ich die "Friendly Interactive SHell" (fish), die es erlaubt, einige Anpassungen vorzunehmen und zusätzlich bereits eingegebene Kommandos beim nächsten Mal automatisch vervollständigen kann. Im Prinzip also "Code Completion" für die Kommandozeile.
Die fish- Shell werden wir später direkt in das Docker- Image installieren. Zusätzlich werden wir
das Theme "emoji-powerline" nutzen. Dafür müssen die
beiden Dateien fish_prompt.fish
und fish_right_prompt.fish
von der Github- Seite heruntergeladen
und im lokalen Ordner ./code-server/config
abgelegt werden. Aus diesem Ordner werden die Dateien
dann später während des Build- Prozesses in das Image kopiert.
Seit Längerem nutze ich als Schriftart in VSCode bzw. dann auch in code-server sehr gerne
Cascadia Code, ein Monospace Font, der auch Code
Ligatures unterstützt. Das sind spezielle Symbole, die anstatt von mehreren zusammenhängenden
Zeichen angezeigt werden und den Code ein bisschen lesbarer machen. So wird zum Beispiel aus =>
ein richtiger Pfeil (⇒).
Und um unserem Motto treu zu bleiben, die Installation auf dem Server so einfach wie möglich zu gestalten, werden wir auch die Dateien für die Schriftart bereits in ein eigenes Docker- Image integrieren.
Dazu wird im Repository ein neuer Ordner ./code-server/fonts
angelegt, in dem wir die woff2
-
Dateien kopieren, die von der
Cascadia Github Seite heruntergeladen wurden.
Des Weiteren wollen wir natürlich auch die Einstellungen in code-server selbst im Github- Repository speichern und wie alle Konfigurationen bisher auch mit dem eigenen code-server- Image bereitstellen.
Wir erstellen also im Ordner ./code-server/config
die Datei settings.json
, in die wir die
Einstellungen aus VSCode komplett übernehmen können. Hier mal exemplarisch meine Einstellungen:
// ./code-server/config/settings.json
{
"workbench.colorTheme": "Cobalt2",
"todo-tree.tree.showScanModeButton": false,
"todo-tree.general.tags": ["BUG", "HACK", "FIXME", "TODO", "XXX", "[ ]", "[x]"],
"todo-tree.regex.regex": "(//|#|<!--|;|/\\*|^|^\\s*(-|\\d+.))\\s*($TAGS)",
"workbench.iconTheme": "material-icon-theme",
"editor.fontFamily": "'Cascadia Code', Consolas, 'Courier New', monospace",
"editor.fontWeight": "600",
"editor.fontLigatures": true,
"terminal.integrated.fontWeight": 600,
"editor.tabSize": 2,
"explorer.openEditors.visible": 0,
"html.format.wrapLineLength": 100,
"editor.minimap.enabled": false,
"editor.suggestSelection": "first",
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.detectIndentation": true,
"editor.rulers": [100],
"editor.snippetSuggestions": "top",
"editor.wordBasedSuggestions": false,
"editor.suggest.localityBonus": true,
"editor.acceptSuggestionOnCommitCharacter": false,
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.suggestSelection": "recentlyUsed",
"editor.suggest.showKeywords": false
},
"editor.renderWhitespace": "boundary",
"files.exclude": {
"USE_GITIGNORE": true
},
"files.defaultLanguage": "{activeEditorLanguage}",
"javascript.validate.enable": false,
"search.exclude": {
"**/node_modules": true,
"**/bower_components": true,
"**/coverage": true,
"**/dist": true,
"**/build": true,
"**/.build": true,
"**/.gh-pages": true
},
"editor.codeActionsOnSave": {
"source.fixAll.eslint": false
},
"eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"],
"eslint.options": {
"overrideConfig": {
"env": {
"browser": true,
"es6": true
},
"parserOptions": {
"ecmaVersion": 2019,
"sourceType": "module",
"ecmaFeatures": {
"jsx": true
}
},
"rules": {
"no-debugger": "off"
}
}
},
"breadcrumbs.enabled": true,
"grunt.autoDetect": "off",
"gulp.autoDetect": "off",
"npm.runSilent": true,
"explorer.confirmDragAndDrop": false,
"editor.formatOnPaste": false,
"editor.cursorSmoothCaretAnimation": true,
"editor.smoothScrolling": true,
"php.suggest.basic": false,
"workbench.tree.indent": 16,
"editor.lineHeight": 20,
"editor.formatOnSave": true,
"prettier.documentSelectors": ["*.md"],
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"files.autoSave": "off",
"conventionalCommits.showNewVersionNotes": false,
"terminal.integrated.defaultProfile.linux": "fish",
"git.confirmSync": false,
"files.associations": {
"*.md": "mdx"
}
}
Ich werde jetzt nicht ins Detail gehen, was die einzelnen Einstellungen jeweils bewirken. Dazu sind es einfach zu viele und jeder hat sich seinen VSCode ja auch ganz individuell angepasst.
Nach diesen ganzen Vorarbeiten können wir nun als Nächstes das Dockerfile erstellen, das die einzelnen Schritte für den Build unseres eigenen code-server- Images definiert.
Als Basis verwende ich ein Image für code-server von linuxserver.io. Jedoch wird nicht die Neueste Version verwendet, da die Entwickler nach der Version 4.0.2 etwas geändert haben, so dass die Schriftarten an einem anderen Ort abgelegt werden müssen. Und ich hatte bisher nicht die Muße, mir das im Detail anzuschauen.
Das Dockerfile erstellen wir also im Ordner ./code-server
und fügen den folgenden Inhalt ein:
# ./code-server/Dockerfile
# base image
FROM linuxserver/code-server:v4.0.2-ls111
# install updates
RUN apt-get update --quiet && apt-get upgrade --yes --quiet
# install nodejs, npm amd yarn
RUN set -ex; \
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add; \
echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list; \
apt-get update --quiet && apt-get install --yes --quiet yarn; \
node --version; \
npm --version; \
yarn --version
# install pnpm
RUN npm install -g pnpm
# add ssh key to access github
RUN mkdir -p /config/.ssh
RUN --mount=type=secret,id=CODE_SERVER_PRIVATE_SSH_KEY_GITHUB cat /run/secrets/CODE_SERVER_PRIVATE_SSH_KEY_GITHUB > /config/.ssh/id_rsa
RUN chmod 600 /config/.ssh/id_rsa
# install fish shell and customize the theme
RUN apt-get install --yes --quiet fish
# apply customizations to fish theme
COPY ./config/fish_prompt.fish /usr/share/fish/functions/
COPY ./config/fish_right_prompt.fish /usr/share/fish/functions/
# install extensions (look for other extensions on https://open-vsx.org/)
RUN set -ex; \
code-server --extensions-dir=/config/extensions \
--install-extension redwan-hossain.auto-rename-tag-clone \
--install-extension mgmcdermott.vscode-language-babel \
--install-extension wesbos.theme-cobalt2 \
--install-extension vivaxy.vscode-conventional-commits \
--install-extension hediet.vscode-drawio \
--install-extension dsznajder.es7-react-js-snippets \
--install-extension dbaeumer.vscode-eslint \
--install-extension mhutchie.git-graph \
--install-extension heybourn.headwind \
--install-extension bierner.markdown-preview-github-styles \
--install-extension PKief.material-icon-theme \
--install-extension anwar.papyrus-pdf \
--install-extension esbenp.prettier-vscode \
--install-extension humao.rest-client \
--install-extension bradlc.vscode-tailwindcss \
--install-extension Gruntfuggly.todo-tree \
--install-extension redhat.vscode-yaml \
--install-extension silvenon.mdx \
--install-extension prisma.prisma
# install font (https://github.com/coder/code-server/issues/1374, https://github.com/tonsky/FiraCode)
COPY ./fonts /usr/local/share/.config/yarn/global/node_modules/code-server/vendor/modules/code-oss-dev/out/vs/code/browser/workbench/fonts
RUN sed -i "s|<head>|\
<style> \n\
@font-face { \n\
font-family: 'Cascadia Code'; \n\
src: url('static/out/vs/code/browser/workbench/fonts/CascadiaCodePL-Light.woff2') format('woff2'); \n\
font-weight: 300; \n\
font-style: normal; \n\
} \n\
@font-face { \n\
font-family: 'Cascadia Code'; \n\
src: url('static/out/vs/code/browser/workbench/fonts/CascadiaCodePL-LightItalic.woff2') format('woff2'); \n\
font-weight: 300; \n\
font-style: italic; \n\
} \n\
@font-face { \n\
font-family: 'Cascadia Code'; \n\
src: url('static/out/vs/code/browser/workbench/fonts/CascadiaCodePL-Regular.woff2') format('woff2'); \n\
font-weight: 400; \n\
font-style: normal; \n\
} \n\
@font-face { \n\
font-family: 'Cascadia Code'; \n\
src: url('static/out/vs/code/browser/workbench/fonts/CascadiaCodePL-Italic.woff2') format('woff2'); \n\
font-weight: 400; \n\
font-style: italic; \n\
} \n\
@font-face { \n\
font-family: 'Cascadia Code'; \n\
src: url('static/out/vs/code/browser/workbench/fonts/CascadiaCodePL-SemiBold.woff2') format('woff2'); \n\
font-weight: 600; \n\
font-style: normal; \n\
} \n\
@font-face { \n\
font-family: 'Cascadia Code'; \n\
src: url('static/out/vs/code/browser/workbench/fonts/CascadiaCodePL-SemiBoldItalic.woff2') format('woff2'); \n\
font-weight: 600; \n\
font-style: italic; \n\
} \n\
@font-face { \n\
font-family: 'Cascadia Code'; \n\
src: url('static/out/vs/code/browser/workbench/fonts/woff2/CascadiaCode-Bold.woff2') format('woff2'); \n\
font-weight: 700; \n\
font-style: normal; \n\
} \n\
@font-face { \n\
font-family: 'Cascadia Code'; \n\
src: url('static/out/vs/code/browser/workbench/fonts/woff2/CascadiaCode-BoldItalic.woff2') format('woff2'); \n\
font-weight: 700; \n\
font-style: italic; \n\
} \n\
\n\</style><head>|g" /usr/local/share/.config/yarn/global/node_modules/code-server/vendor/modules/code-oss-dev/out/vs/code/browser/workbench/workbench.html
# copy settings file
RUN mkdir -p /config/data/User
COPY ./config/settings.json /config/data/User/settings.json
# set git config
RUN set -ex; \
git config --global user.name "<GITHUB_USERNAME>"; \
git config --global user.email "<ME@EMAIL.COM>"
Die Schritte sind im Einzelnen:
CODE_SERVER_PRIVATE_SSH_KEY_GITHUB
.Nun sind alle Voraussetzungen geschaffen, um unser eigenes Image von code-server zu erstellen. Dazu definieren wir wieder eine Github Action, die das Image automatisch erneut erstellt, sobald Änderungen an den Konfigurationsdateien per Commit auf Github gepusht werden.
# ./.github/workflows/docker-build-code-server.yml
name: build custom image for code-server
on:
push:
branches:
- 'master'
paths:
- 'code-server/**'
jobs:
build-code-server:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v2
- name: Set up Docker BuildX
uses: docker/setup-buildx-action@v1
- name: login to GitHub Container Registry
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and Push the custom Code-Server Image
uses: docker/build-push-action@v2
with:
context: ./code-server
push: true
tags: ghcr.io/<GITHUB_USERNAME>/code-server:latest
secrets: |
"CODE_SERVER_PRIVATE_SSH_KEY_GITHUB=${{ secrets.CODE_SERVER_PRIVATE_SSH_KEY_GITHUB }}"
Das Schema dieser Github Action ähnelt natürlich wieder den Actions aus dem zweiten Teil, die das Image für Authelia oder Traefik erstellen.
Eine Besonderheit ist jedoch, dass wir mit der Umgebungsvariable
CODE_SERVER_PRIVATE_SSH_KEY_GITHUB
einen privaten SSH- Key, im Docker-Image ablegen, um später auf
Github zugreifen zu können. Der dazugehörigen Public Key wird in den Einstellungen von Github
gespeichert. Eine Anleitung zum Erstellen von SSH- Keys kann
hier gefunden werden.
docker-compose.yml
Als nächstes müssen wir unsere docker-compose.yml
erweitern, um den Docker- Container für
code-server starten zu können:
# ./docker-compose.yml
volumes:
# ...
code-server-data:
driver: local
services:
# ...
code-server:
image: ghcr.io/<GITHUB_USERNAME>/code-server:latest
container_name: code-server
hostname: code-server
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/Berlin
- SUDO_PASSWORD=123
volumes:
- code-server-data:/config
networks:
- proxy
expose:
- 8443
- 3000 # nextjs & react development server
- 4200 # nx development server
- 4400 # nx storybook
restart: unless-stopped
labels:
- 'traefik.enable=true'
- 'traefik.docker.network=proxy'
- 'com.centurylinklabs.watchtower.enable=true'
# code-server app
- 'traefik.http.routers.code-server.rule=Host(`vscode.server.<MY-DOMAIN>`)'
- 'traefik.http.routers.code-server.entrypoints=https'
- 'traefik.http.routers.code-server.tls=true'
- 'traefik.http.routers.code-server.tls.certresolver=letsencrypt'
- 'traefik.http.routers.code-server.middlewares=authelia@docker'
- 'traefik.http.routers.code-server.service=code-server-service'
- 'traefik.http.services.code-server-service.loadbalancer.server.port=8443'
# port 3000
- 'traefik.http.routers.code-server-port-3000.rule=Host(`port-3000.server.<MY-DOMAIN>`)'
- 'traefik.http.routers.code-server-port-3000.entrypoints=https'
- 'traefik.http.routers.code-server-port-3000.tls=true'
- 'traefik.http.routers.code-server-port-3000.tls.certresolver=letsencrypt'
- 'traefik.http.routers.code-server-port-3000.middlewares=authelia@docker'
- 'traefik.http.routers.code-server-port-3000.service=code-server-port-3000-service'
- 'traefik.http.services.code-server-port-3000-service.loadbalancer.server.port=3000'
# port 4200
- 'traefik.http.routers.code-server-port-4200.rule=Host(`port-4200.server.<MY-DOMAIN>`)'
- 'traefik.http.routers.code-server-port-4200.entrypoints=https'
- 'traefik.http.routers.code-server-port-4200.tls=true'
- 'traefik.http.routers.code-server-port-4200.tls.certresolver=letsencrypt'
- 'traefik.http.routers.code-server-port-4200.middlewares=authelia@docker'
- 'traefik.http.routers.code-server-port-4200.service=code-server-port-4200-service'
- 'traefik.http.services.code-server-port-4200-service.loadbalancer.server.port=4200'
# port 4400
- 'traefik.http.routers.code-server-port-4400.rule=Host(`port-4400.server.<MY-DOMAIN>`)'
- 'traefik.http.routers.code-server-port-4400.entrypoints=https'
- 'traefik.http.routers.code-server-port-4400.tls=true'
- 'traefik.http.routers.code-server-port-4400.tls.certresolver=letsencrypt'
- 'traefik.http.routers.code-server-port-4400.middlewares=authelia@docker'
- 'traefik.http.routers.code-server-port-4400.service=code-server-port-4400-service'
- 'traefik.http.services.code-server-port-4400-service.loadbalancer.server.port=4400'
Auffallend ist hier, dass wir gleich vier Ports freigeben und über den Traefik Reverse Proxy aus dem Internet erreichbar machen. Auf Port 8443 läuft dabei code-server selbst, dieser Port ist also zwingend notwendig. Die anderen Ports werden zum Aufrufen des Entwicklungsservers für verschiedene Frameworks über den Browser benötigt und können individuell angepasst werden.
Wenn man zum Beispiel lokal eine App mit create-react-app
initialisiert hat und diese dann mit
npm run start
startet, kann man diese App standardmäßig auf Port 3000 aufrufen. In unserem Fall
würde man dann die Seite über die URL port-3000.server.<MY-DOMAIN>
aufrufen können.
Beim Start des Entwicklungsservers über zum Beispiel npm run start
für React Apps wird für alle
Dateien des Projekts ein "File Watcher" mit inotify
registriert, so dass Änderungen an den
Projektdateien erkannt werden können und die App erneut kompiliert werden kann.
Da aber für alle Dateien, die auf diese Weise überwacht werden, natürlich Resourcen verbraucht werden, ist die Anzahl an Dateien standardmäßig limitiert. Man kann dieses Limit mit dem folgenden Befehl abfragen:
sysctl fs.inotify.max_user_watches
Mit den vielen Node Modules, die für eine Web App genutzt werden, ist dieses Limit aber sehr schnell erreicht und es wird eine Fehlermeldung ausgegeben:
Watchpack Error (watcher): Error: ENOSPC: System limit for number of file watchers reached, watch '/some/path'
Um das Limit zu erhöhen (hier auf 524288) muss der folgende Befehl auf dem Host- System (also direkt auf dem Server) ausgeführt werden:
echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p
Jetzt haben wir alle Konfigurationen für den Betrieb von code-server und der Bereitstellung des
Servers im Internet vorgenommen und können des Repository nach dem Erstellen eines Commit mit dem
Befehl git push origin main
auf Gihub hochladen.
Wenn wir anschließend auf der Github- Webseite für das Repository den Tab "Actions" aufrufen, sollten wir direkt sehen, dass drei Actions ausgeführt werden (je eine für das Erstellen unserer eigenen Images für Authelia, Traefik und code-server).
Wenn alle Actions erfolgreich fertiggestellt wurden, können wir uns per SSH auf dem Server anmelden und das Repository klonen. Danach wechseln wir in das Verzeichnis des Repositorys, in dem auch die `docker-compose.yml' liegt und können mit dem folgenden Befehl alle notwendigen Container herunterladen und starten:
docker-compose up -d
Beim ersten Start empfiehlt es sich eventuell das -d
wegzulassen, so dass alle Meldungen der
Container direkt im Terminal ausgegeben werden und eventuell auftretende Fehler leichter erkannt
werden können. Für den späteren Betrieb ist dieses Flag für den "detached"- Modus jedoch notwendig,
da sonst alle Container gestoppt werden, sobald wir uns vom Server abmelden wollen.
Nachdem alle Container gestartet sind, können wir code-server zum ersten Mal auf der URL
vscode.server.<MY-DOMAIN>
aufrufen.
Bevor wir jedoch auf den code-server weitergeleitet werden, müssen wir uns bei Authelia anmelden und die Zwei- Faktor- Authentifizierung einrichten. Dazu erhalten wir eine E-Mail mit einem Link, über den ein QR- Code für die Verknüpfung des Smartphones mit Authelia aufgerufen werden kann. Dazu muss einfach der QR- Code mit dem Smartphone und einer App wie zum Beispiel (Google Authenticator](https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&hl=de&gl=US) abgescannt werden. Anschließend wird auf dem Smartphone in regelmäßigen Abständen ein sechsstelliger Zahlencode generiert, der dann bei Authelia als zweiter Faktor für die Authentifizierung neben dem Passwort eingegeben werden muss.
Wenn das alles erledigt ist, sollten wir direkt zu code-server weitergeleitet werden.
Theoretisch könnte man jetzt direkt mit dem Coden loslegen. Da aber code-server als "Progressive Web App" (PWA) entwickelt uwrde, kann man code-server in vielen Browsern als Desktopanwendung installieren. Danach lässt sich code-server fast wie eine lokal installierte Instanz von VSCode bedienen
Prinzipiell ist der Server für die cloudbasiert Webentwicklung nun komplett und wir könnten mit dem Coden loslegen.
Im letzten Teil zeige ich aber noch wie man ein Monitoring für den Server einrichten kann, so dass man alle wichtigen Leistungsdaten (CPU- Auslastung, Speichernutzung, etc.) überwachen kann.