Skip to content

Devcontainer improvements #1137

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 24 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
## __WORK IN PROGRESS__
(at the beginning of a new line)
-->
## __WORK IN PROGRESS__
* (hacki11) Dev Container improvements (#1137) · [Migration guide](docs/updates/20250404_devcontainer_improvments.md)
* (hacki11) Allow newer versions of `admin` (#1137) · [Migration guide](docs/updates/20250406_admin_dependency_ge.md)

## 2.6.5 (2024-09-13)
* (AlCalzone) Update required versions of `js-controller` and `admin` to the current stable versions (#1116)
* (AlCalzone) Remove deprecated `main` and `title` fields from `io-package.json` (#1115)
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
506 changes: 506 additions & 0 deletions docs/updates/20250404_devcontainer_improvments.md

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions docs/updates/20250406_admin_dependency_ge.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Update Global Dependency `admin` to Use Greater Than Equal (`>=`)

To allow support for newer versions of the `admin` dependency, the version constraint in `io-package.json` has been updated from a fixed version to greater or equal (`>=`) version.

`io-package.json`
```diff
"globalDependencies": [
{
- "admin": "7.0.23"
+ "admin": ">=7.0.23"
}
]
```
9 changes: 8 additions & 1 deletion templates/_devcontainer/README.md.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,14 @@ const templateFunction: TemplateFunction = answers => {
# Devcontainer readme
This directory allows you to develop your adapter in a dedicated Docker container. To get started and for requirements, please read the getting started section at https://code.visualstudio.com/docs/remote/containers#_getting-started

Once you're done with that, VSCode will prompt you to reopen the adapter directory in a container. This takes a while, afterwards you can access the admin UI at http://localhost:8082. To change that port, edit \`docker-compose.yml\` to have a different \`ports\` configuration for \`nginx\`.
Once you're done with that, VSCode will prompt you to reopen the adapter directory in a container. This takes a while. Afterward, you can access the admin UI by the forwarded port **Ports** panel in VS Code.

1. Open the **Ports** panel in VS Code (look for the "Ports" tab in the bottom panel or use the Command Palette with \`Ctrl+Shift+P\` and search for "Ports: Focus on Ports View").
2. Locate the forwarded port for the admin UI (usually dynamically assigned).
3. Click the link in the **Ports** panel to open the admin UI in your browser.

By default, the admin UI is available at \`http://localhost:<forwarded-port>\`.
To change the port, edit \`devcontainer.json\` to have a different \`forwardPorts\` configuration for \`nginx\`.
`;
return template.trim();
};
Expand Down
34 changes: 25 additions & 9 deletions templates/_devcontainer/devcontainer.json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const templateFunction: TemplateFunction = answers => {
const devcontainer = answers.tools && answers.tools.includes("devcontainer");
if (!devcontainer) return;

const adapterNameLowerCase = answers.adapterName.toLowerCase();
const useESLint = !!answers.tools?.includes("ESLint");
const usePrettier = !!answers.tools?.includes("Prettier");

Expand All @@ -17,12 +18,22 @@ const templateFunction: TemplateFunction = answers => {
// https://github.com/microsoft/vscode-dev-containers/tree/v0.101.1/containers/docker-existing-docker-compose
// If you want to run as a non-root user in the container, see .devcontainer/docker-compose.yml.
{
"name": "ioBroker Docker Compose",
"name": "ioBroker.${adapterNameLowerCase}",

// Update the 'dockerComposeFile' list if you have more compose files or use different names.
// The .devcontainer/docker-compose.yml file contains any overrides you need/want to make.
"dockerComposeFile": [ "docker-compose.yml" ],

// Forwarding the nginx port to access ioBroker Admin interface
"forwardPorts": ["nginx:80"],

// Name of the forwarded port
"portsAttributes": {
"nginx:80": {
"label": "ioBroker Admin UI"
}
},

// The 'service' property is the name of the service for the container that VS Code should
// use. Update this value and .devcontainer/docker-compose.yml to the real service name.
"service": "iobroker",
Expand All @@ -31,23 +42,28 @@ const templateFunction: TemplateFunction = answers => {
// connected. This is typically a file mount in .devcontainer/docker-compose.yml
"workspaceFolder": "/workspace",

// Set *default* container specific settings.json values on container create.
"settings": {},
"customizations": {
"vscode": {
// Set *default* container specific settings.json values on container create.
"settings": {},

// Add the IDs of extensions you want installed when the container is created.
"extensions": ${JSON.stringify(extensions)},
// Add the IDs of extensions you want installed when the container is created.
"extensions": ${JSON.stringify(extensions)}
}
},

// Uncomment the next line if you want start specific services in your Docker Compose config.
// "runServices": [],

// Uncomment the next line if you want to keep your containers running after VS Code shuts down.
// "shutdownAction": "none",

// When creating the container, delete unnecessary adapters, disable error reporting, set the license as confirmed, and install/update this adapter
"postCreateCommand": "iob del discovery && iob plugin disable sentry && iob object set system.config common.licenseConfirmed=true && NPM_PACK=$(npm pack) && iob url \\\"$(pwd)/$NPM_PACK\\\" --debug && rm \\\"$NPM_PACK\\\""
// Prepare the devcontainer according to the actual adapter
"postCreateCommand": "sh .devcontainer/scripts/postcreate.sh",
"postStartCommand": "sh .devcontainer/scripts/poststart.sh",

// Uncomment to connect as a non-root user. See https://aka.ms/vscode-remote/containers/non-root.
//"remoteUser": "iobroker"
// Connect as non-root user. See https://aka.ms/vscode-remote/containers/non-root.
"remoteUser": "iobroker"
}
`;
return template.trim();
Expand Down
17 changes: 9 additions & 8 deletions templates/_devcontainer/docker-compose.yml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ const templateFunction: TemplateFunction = answers => {
const needsParcel = answers.adminUi === "react" || answers.tabReact === "yes";

const template = `
version: '3'

services:
iobroker:
build: ./iobroker
Expand All @@ -21,8 +19,14 @@ services:
- 8081
volumes:
- ..:/workspace:cached
- iobrokerdata-${adapterNameLowerCase}:/opt/iobroker
environment:
# using non-default ports to not interfere with integration tests
- IOB_OBJECTSDB_TYPE=jsonl
- IOB_OBJECTSDB_HOST=127.0.0.1
- IOB_OBJECTSDB_PORT=29001
- IOB_STATESDB_TYPE=jsonl
- IOB_STATESDB_HOST=127.0.0.1
- IOB_STATESDB_PORT=29000
- LANG=en_US.UTF-8
- LANGUAGE=en_US:en
- LC_ALL=en_US.UTF-8
Expand Down Expand Up @@ -55,11 +59,8 @@ ${needsParcel ? (` - parcel
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
- ..:/workspace:cached
ports:
# Make the ioBroker admin available under http://localhost:8082
- 8082:80

volumes:
iobrokerdata-${adapterNameLowerCase}:
# Port will be forwarded in the devcontainer
- 80
`;
return template.trim();
};
Expand Down
21 changes: 20 additions & 1 deletion templates/_devcontainer/iobroker/_Dockerfile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,26 @@ const templateFunction: TemplateFunction = answers => {

const template = `
FROM iobroker/iobroker:latest
RUN ln -s /opt/iobroker/node_modules/ /root/.node_modules
RUN ln -s /opt/iobroker/node_modules/ /node_modules

COPY node-wrapper.sh /usr/bin/node-wrapper.sh
RUN chmod +x /usr/bin/node-wrapper.sh \\
&& NODE_BIN="$(command -v node)" \\
# Move the original node binary to .real
&& mv "$NODE_BIN" "$\{NODE_BIN\}.real" \\
# Move the wrapper in place
&& mv /usr/bin/node-wrapper.sh "$NODE_BIN"

# Support sudo for non-root user
ARG USERNAME=iobroker
RUN echo $USERNAME ALL=\\(root\\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \\
&& chmod 0440 /etc/sudoers.d/$USERNAME

COPY boot.sh /opt/iobroker/boot.sh
RUN chmod +x /opt/iobroker/boot.sh \\
&& mkdir -p /opt/iobroker/log

ENTRYPOINT ["/bin/bash", "-c", "/opt/iobroker/boot.sh"]
`;
return template.trim();
};
Expand Down
26 changes: 26 additions & 0 deletions templates/_devcontainer/iobroker/boot.sh.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { TemplateFunction } from "../../../src/lib/createAdapter";

const templateFunction: TemplateFunction = answers => {

const devcontainer = answers.tools && answers.tools.includes("devcontainer");
if (!devcontainer) return;

const template = `
#!/bin/bash

set -e

# Define log file location
LOG_FILE=/opt/iobroker/log/boot.log
mkdir -p /opt/iobroker/log

# Start logging to the file (standard output and error)
exec > >(tee "$LOG_FILE") 2>&1

/opt/scripts/iobroker_startup.sh
`;
return template.trim();
};

templateFunction.customPath = ".devcontainer/iobroker/boot.sh";
export = templateFunction;
36 changes: 36 additions & 0 deletions templates/_devcontainer/iobroker/node-wrapper.sh.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { TemplateFunction } from "../../../src/lib/createAdapter";

const templateFunction: TemplateFunction = answers => {

const devcontainer = answers.tools && answers.tools.includes("devcontainer");
if (!devcontainer) return;

const template = `
#!/bin/bash

# Wrap the Node.js binary to handle NODE_OPTIONS as command-line arguments.
# This workaround addresses https://github.com/nodejs/node/issues/37588, where
# NODE_OPTIONS is not respected when running Node.js with capabilities as a non-root user.
# The wrapper script reads the NODE_OPTIONS environment variable and converts it into
# standard command-line arguments. For example:
# NODE_OPTIONS=--inspect node main.js
# becomes:
# node.real --inspect main.js
# This ensures debugging, and other features relying on NODE_OPTIONS work properly
# for non-root users, such as in VS Code Remote Containers.

NODE_ARGS=()

if [[ -n "$NODE_OPTIONS" ]]; then
eval "read -r -a NODE_ARGS <<< \\"$NODE_OPTIONS\\""
unset NODE_OPTIONS
fi

REAL_NODE="$(command -v node).real"
exec "$REAL_NODE" "$\{NODE_ARGS[@]\}" "$@"
`;
return template.trim();
};

templateFunction.customPath = ".devcontainer/iobroker/node-wrapper.sh";
export = templateFunction;
6 changes: 5 additions & 1 deletion templates/_devcontainer/nginx/nginx.conf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,19 @@ http {
listen 80;

location / {
error_page 418 = @websocket;
proxy_redirect off;
proxy_pass http://iobroker:8081;
if ( $args ~ "sid=" ) { return 418; }
}

location /socket.io/ {
location @websocket {
proxy_pass http://iobroker:8081;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_read_timeout 86400;
proxy_send_timeout 86400;
}

location /adapter/${adapterNameLowerCase}/ {
Expand Down
53 changes: 53 additions & 0 deletions templates/_devcontainer/scripts/postcreate.sh.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import type { TemplateFunction } from "../../../src/lib/createAdapter";

const templateFunction: TemplateFunction = answers => {

const devcontainer = answers.tools && answers.tools.includes("devcontainer");
if (!devcontainer) return;

const template = `
#!/bin/bash

set -e

# Wait for ioBroker to become ready
sh .devcontainer/scripts/wait_for_iobroker.sh

echo "➡️ Install dependencies"
npm install

echo "➡️ Packaging adapter"
NPM_PACK=$(npm pack)

echo "➡️ Delete discovery adapter"
iob del discovery

echo "➡️ Disable error reporting"
iob plugin disable sentry

echo "➡️ Disable sending diagnostics"
iob object set system.config common.diag=false

echo "➡️ Set the license as confirmed"
iob object set system.config common.licenseConfirmed=true

echo "➡️ Install the adapter"
iob url "$(pwd)/$NPM_PACK" --debug

ADAPTER_NAME=$(jq -r '.name | split(".")[1]' package.json)
echo "➡️ Create a $ADAPTER_NAME instance"
iob add $ADAPTER_NAME

echo "➡️ Stop $ADAPTER_NAME instance"
iob stop $ADAPTER_NAME

echo "➡️ Delete the adapter package"
rm "$NPM_PACK"

touch /tmp/.postcreate_done
`;
return template.trim();
};

templateFunction.customPath = ".devcontainer/scripts/postcreate.sh";
export = templateFunction;
25 changes: 25 additions & 0 deletions templates/_devcontainer/scripts/poststart.sh.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { TemplateFunction } from "../../../src/lib/createAdapter";

const templateFunction: TemplateFunction = answers => {

const devcontainer = answers.tools && answers.tools.includes("devcontainer");
if (!devcontainer) return;

const template = `
#!/bin/bash

set -e

# execute poststart only if container was created right before
if [ -e /tmp/.postcreate_done ]; then
rm /tmp/.postcreate_done
else
# Wait for ioBroker to become ready
sh .devcontainer/scripts/wait_for_iobroker.sh
fi
`;
return template.trim();
};

templateFunction.customPath = ".devcontainer/scripts/poststart.sh";
export = templateFunction;
47 changes: 47 additions & 0 deletions templates/_devcontainer/scripts/wait_for_iobroker.sh.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import type { TemplateFunction } from "../../../src/lib/createAdapter";

const templateFunction: TemplateFunction = answers => {

const devcontainer = answers.tools && answers.tools.includes("devcontainer");
if (!devcontainer) return;

const template = `
#!/bin/bash

set -e

# Start tailing the iobroker boot log and kill it when the script exits
tail -f -n 100 /opt/iobroker/log/boot.log &
TAIL_PID_BOOT=$!

# Ensure the tail process is killed when the script exits
trap "kill $TAIL_PID_BOOT" EXIT

# wait for ioBroker to become ready
echo "⏳ Waiting for ioBroker to become ready..."

ATTEMPTS=20
SLEEP=0.5
i=1

while [ $i -le $ATTEMPTS ]; do
if iob status > /dev/null 2>&1; then
echo "✅ ioBroker is ready."
break
else
echo "⌛ Attempt $i/$ATTEMPTS: Waiting for ioBroker..."
sleep $SLEEP
i=$((i + 1))
fi
done

if ! iob status > /dev/null 2>&1; then
echo "❌ Timeout: ioBroker did not become ready in time"
exit 1
fi
`;
return template.trim();
};

templateFunction.customPath = ".devcontainer/scripts/wait_for_iobroker.sh";
export = templateFunction;
Loading