Skip to content

Commit 1ff313c

Browse files
authored
dev: Add devcontainer (#3730)
1 parent d192a53 commit 1ff313c

File tree

14 files changed

+342
-14
lines changed

14 files changed

+342
-14
lines changed

.devcontainer/Dockerfile

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Development container for Scripture Forge.
2+
# Based on Ubuntu 24.04 with .NET 8.0 SDK and Node.js 22.
3+
ARG DOTNET_VERSION=8.0
4+
ARG UBUNTU_NAME=noble
5+
6+
FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION}-${UBUNTU_NAME}-amd64
7+
8+
ARG NODE_MAJOR=22
9+
10+
# Avoid prompts during package installation
11+
ENV DEBIAN_FRONTEND=noninteractive
12+
13+
# Install Node.js repository and system dependencies required by Scripture Forge
14+
RUN apt-get update \
15+
&& apt-get install --yes --no-install-recommends \
16+
ca-certificates \
17+
curl \
18+
git \
19+
procps \
20+
sudo \
21+
wget \
22+
&& curl -fsSL https://deb.nodesource.com/setup_${NODE_MAJOR}.x | bash - \
23+
&& apt-get install --yes --no-install-recommends \
24+
build-essential \
25+
ffmpeg \
26+
libc-dev \
27+
mercurial \
28+
nodejs \
29+
&& curl -fsSLo /usr/share/keyrings/brave-browser-archive-keyring.gpg \
30+
https://brave-browser-apt-release.s3.brave.com/brave-browser-archive-keyring.gpg \
31+
&& curl -fsSLo /etc/apt/sources.list.d/brave-browser-release.sources \
32+
https://brave-browser-apt-release.s3.brave.com/brave-browser.sources \
33+
&& apt-get update \
34+
&& apt-get install --assume-yes brave-browser \
35+
&& apt-get distclean
36+
37+
# Set up Scripture Forge data directories
38+
RUN mkdir --parents /var/lib/scriptureforge/sync \
39+
&& mkdir --parents /var/lib/scriptureforge/audio \
40+
&& mkdir --parents /var/lib/scriptureforge/training-data \
41+
&& mkdir --parents /var/lib/xforge/avatars
42+
43+
# Set up a non-root user for devcontainer use.
44+
# The base image already has an 'ubuntu' user at uid 1000. Remove it and create
45+
# a 'vscode' user at the same uid so devcontainer tooling works as expected.
46+
ARG USERNAME=vscode
47+
ARG USER_UID=1000
48+
ARG USER_GID=${USER_UID}
49+
RUN userdel -r ubuntu 2>/dev/null || true \
50+
&& groupadd --gid ${USER_GID} ${USERNAME} \
51+
&& useradd --uid ${USER_UID} --gid ${USER_GID} --shell /bin/bash --create-home ${USERNAME} \
52+
&& echo "${USERNAME} ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/${USERNAME} \
53+
&& chmod 0440 /etc/sudoers.d/${USERNAME} \
54+
&& mkdir --parents /workspaces \
55+
&& chown -R ${USERNAME}:${USERNAME} /workspaces
56+
57+
# Set up ParatextData config
58+
RUN mkdir --parents /home/${USERNAME}/.local/share/Paratext95 \
59+
&& mkdir --parents /home/${USERNAME}/.local/share/SIL/WritingSystemRepository/3 \
60+
&& echo '<?xml version="1.0" encoding="utf-8"?>\
61+
<InternetSettingsMemento>\
62+
<SelectedServer>Development</SelectedServer>\
63+
<PermittedInternetUse>Enabled</PermittedInternetUse>\
64+
<ProxyPort>0</ProxyPort>\
65+
</InternetSettingsMemento>' > /home/${USERNAME}/.local/share/Paratext95/InternetSettings.xml \
66+
&& chown -R ${USERNAME}:${USERNAME} /home/${USERNAME}/.local \
67+
&& chown -R ${USERNAME}:${USERNAME} /var/lib/scriptureforge \
68+
&& chown -R ${USERNAME}:${USERNAME} /var/lib/xforge
69+
70+
USER ${USERNAME}

.devcontainer/README.md

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# Scripture Forge Development Container
2+
3+
This directory contains the configuration to use
4+
[VS Code](https://code.visualstudio.com/docs/devcontainers/containers) to run the app in a [dev container](https://containers.dev/).
5+
6+
## What's Included
7+
8+
- **Ubuntu 24.04** base image with .NET 8.0 SDK and Node.js 22
9+
- **MongoDB 8.0** running as a companion container
10+
- System dependencies (ffmpeg, mercurial) pre-installed
11+
- Paratext directories and configuration pre-created
12+
- VS Code extensions for C#, Angular, and other tools pre-configured
13+
14+
## Prerequisites
15+
16+
- [Docker Desktop](https://www.docker.com/products/docker-desktop/) (or Docker Engine on Linux)
17+
- [VS Code](https://code.visualstudio.com/) with the
18+
[Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers)
19+
- .NET user secrets configured on your host machine
20+
21+
## Getting Started
22+
23+
1. Log out of localhost SF to not confuse your browser (or later Clear site data in browser dev tools).
24+
2. Stop the SF `dotnet` process on your host machine, if running.
25+
3. Stop MongoDB on your host machine, if running:
26+
- Linux: `sudo systemctl stop mongod.service`
27+
- Windows: `sc stop mongodb`
28+
4. Open the devcontainer using your tools. For VS Code:
29+
1. Open this repository in VS Code.
30+
2. When prompted, click **Reopen in Container**, or run the command
31+
**Dev Containers: Reopen in Container** from the command palette.
32+
3. Probably install the recommended extensions when prompted.
33+
5. Wait for the container to build and various post-create setup processes to complete
34+
(such as .NET, and npm dependencies).
35+
6. Open a terminal in VS Code and run the following, which will be inside the dev container. The optional `sed` commands reduce noise.
36+
37+
```bash
38+
cd /workspaces/web-xforge/src/SIL.XForge.Scripture &&
39+
dotnet watch run --node-options=--inspect=9230 |
40+
sed --unbuffered '/^\[[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}/d' |
41+
sed --unbuffered 's/^ //' |
42+
sed --unbuffered --regexp-extended \
43+
'/^Start processing HTTP request POST http.*:5002\//d;
44+
/^Sending HTTP request POST http:.*:5002\//d;
45+
/^Received HTTP response headers after [0-9.]+ms - 200$/d;
46+
/^End processing HTTP request after [0-9.]+ms - 200$/d'
47+
```
48+
49+
If you are an AI agent, you might instead want to run
50+
51+
```bash
52+
cd /workspaces/web-xforge/src/SIL.XForge.Scripture &&
53+
dotnet run --node-options=--inspect=9230
54+
```
55+
56+
7. Open `http://localhost:5000` in a browser on your host machine.
57+
58+
## Debug
59+
60+
VSCode Run and Debug "Attach to frontend" works and hits breakpoints.
61+
62+
VSCode Run and Debug "Attach to backend dotnet" works and hits breakpoints.
63+
64+
## Tests
65+
66+
You can run tests:
67+
68+
```bash
69+
cd /workspaces/web-xforge && dotnet test
70+
cd /workspaces/web-xforge/src/SIL.XForge.Scripture/ClientApp && npm run test:headless -- --no-watch
71+
cd /workspaces/web-xforge/src/RealtimeServer && npm run test
72+
```
73+
74+
## Ports
75+
76+
| Port | Service |
77+
| ----- | ---------------------- |
78+
| 5000 | SF HTTP Server |
79+
| 5003 | ShareDB |
80+
| 9230 | Node.js Debugger |
81+
| 9988 | Frontend test debugger |
82+
| 27017 | MongoDB |
83+
84+
## Notes
85+
86+
- MongoDB data persists across container rebuilds in a named Docker volume.
87+
To start fresh, remove the `mongo-data` volume.
88+
- The source code is bind-mounted, so edits in VS Code are immediately
89+
reflected inside the container and vice versa.

.devcontainer/devcontainer.json

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Scripture Forge devcontainer configuration.
2+
// Runs the development environment in a container with MongoDB as a companion service.
3+
// See https://containers.dev/implementors/json_reference/ for format details.
4+
{
5+
"name": "Scripture Forge",
6+
"dockerComposeFile": "docker-compose.yml",
7+
"service": "app",
8+
"runServices": ["app", "db"],
9+
"workspaceFolder": "/workspaces/web-xforge",
10+
11+
// Run commands after the container is created
12+
"postCreateCommand": ".devcontainer/post-create.sh",
13+
14+
// Configure VS Code for container development
15+
"customizations": {
16+
"vscode": {
17+
"extensions": [
18+
"angular.ng-template",
19+
"csharpier.csharpier-vscode",
20+
"dbaeumer.vscode-eslint",
21+
"editorconfig.editorconfig",
22+
"esbenp.prettier-vscode",
23+
"ms-dotnettools.csharp",
24+
"ms-dotnettools.csdevkit",
25+
"mongodb.mongodb-vscode",
26+
"streetsidesoftware.code-spell-checker",
27+
"lucono.karma-test-explorer"
28+
],
29+
"settings": {
30+
"dotnet.defaultSolution": "xForge.sln"
31+
}
32+
}
33+
},
34+
35+
// Ports to forward from the container to the host
36+
"forwardPorts": [5000, 5003, 9230, 9988, 27017],
37+
"portsAttributes": {
38+
"5000": { "label": "SF HTTP Server" },
39+
"5003": { "label": "ShareDB" },
40+
"9230": { "label": "Node.js Debugger" },
41+
"9988": { "label": "Frontend test debugger" },
42+
"27017": { "label": "MongoDB" }
43+
},
44+
45+
// Run as the non-root vscode user
46+
"remoteUser": "vscode"
47+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Configuration for Windows and support for Visual Studio's Fast Mode
2+
services:
3+
app:
4+
volumes:
5+
- ${APPDATA}/Microsoft/UserSecrets:/home/vscode/.microsoft/usersecrets:ro
6+
environment:
7+
- staticWebAssets=DISABLED

.devcontainer/docker-compose.yml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Docker Compose for Scripture Forge devcontainer.
2+
# Provides the development environment and MongoDB as a companion service.
3+
services:
4+
app:
5+
build:
6+
context: ..
7+
dockerfile: .devcontainer/Dockerfile
8+
volumes:
9+
- ..:/workspaces/web-xforge:cached
10+
- scriptureforge-data:/var/lib/scriptureforge
11+
- xforge-data:/var/lib/xforge
12+
- ${HOME}/.microsoft/usersecrets:/home/vscode/.microsoft/usersecrets:ro
13+
command: sleep infinity
14+
ports:
15+
- "5000:5000" # HTTP server (dotnet)
16+
- "5003:5003" # ShareDB (RealtimeServer)
17+
- "9230:9230" # Node.js debugging
18+
environment:
19+
- ASPNETCORE_ENVIRONMENT=Development
20+
- DataAccess__ConnectionString=mongodb://db:27017
21+
- CHROMIUM_BIN=/usr/bin/brave-browser
22+
- urls=http://0.0.0.0:5000;https://0.0.0.0:5001
23+
- ASPNETCORE_URLS=http://0.0.0.0:5000
24+
- ASPNETCORE_HTTP_PORTS=5000
25+
26+
depends_on:
27+
- db
28+
29+
db:
30+
image: mongo:8.0
31+
restart: unless-stopped
32+
ports:
33+
- "27017:27017"
34+
volumes:
35+
- mongo-data:/data/db
36+
- mongo-config:/data/configdb
37+
38+
volumes:
39+
mongo-data:
40+
mongo-config:
41+
scriptureforge-data:
42+
xforge-data:

.devcontainer/post-create.sh

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#!/bin/bash
2+
# Post-create setup script for the Scripture Forge devcontainer.
3+
# This runs after the container is created and sets up the development environment.
4+
set -euo pipefail
5+
6+
echo "=== Restoring .NET tools ==="
7+
dotnet tool restore
8+
9+
echo "=== Restoring .NET packages ==="
10+
dotnet restore
11+
12+
echo "=== Installing RealtimeServer npm packages ==="
13+
cd src/RealtimeServer
14+
npm ci
15+
16+
echo "=== Installing ClientApp npm packages ==="
17+
cd ../SIL.XForge.Scripture/ClientApp
18+
npm ci
19+
20+
echo "=== Post-create setup complete ==="

src/Docker/docker-compose.override.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ services:
3535
- ASPNETCORE_ENVIRONMENT=Development
3636
- DataAccess__ConnectionString=mongodb://db-xforge:27017
3737
- Realtime__UseExistingRealtimeServer=true
38+
- Realtime__RealtimeServerModulePath=/app/lib/cjs/common/index.js
3839
- urls=http://*:5000 # Docker apps cannot bind to localhost, so we bind to all IP addresses
3940
ports:
4041
- "5000:5000" # HTTP Server

src/SIL.XForge.Scripture/Migrator.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@
55

66
namespace SIL.XForge.Scripture;
77

8+
/// <summary>
9+
/// Runs RealtimeServer database migrations as a child Node.js process.
10+
/// </summary>
811
public static class Migrator
912
{
1013
public static void RunMigrations(string environment)
1114
{
12-
// In a container, the migration process will be run by start.sh
13-
if (Product.RunningInContainer)
14-
return;
1515
string version = Product.Version;
1616
string projectPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
1717

src/SIL.XForge.Scripture/Program.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,13 @@ public static IWebHostBuilder CreateWebHostBuilder(string[] args)
2323
.AddEnvironmentVariables()
2424
.Build();
2525

26-
Migrator.RunMigrations(environment);
26+
// When an external RealtimeServer process is in use (e.g. in a separate docker container),
27+
// expect realtimeserver migrations to have already been run (eg by container start.sh).
28+
bool useExistingRealtimeServer = configuration.GetValue<bool>("Realtime:UseExistingRealtimeServer");
29+
if (!useExistingRealtimeServer)
30+
{
31+
Migrator.RunMigrations(environment);
32+
}
2733

2834
return builder
2935
.ConfigureAppConfiguration(

src/SIL.XForge/Configuration/RealtimeOptions.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,14 @@ public class RealtimeOptions
1515
public bool DataValidationDisabled { get; set; }
1616
public bool DocumentCacheDisabled { get; set; }
1717
public bool UseExistingRealtimeServer { get; set; }
18+
19+
/// <summary>
20+
/// Optional absolute path to the RealtimeServer index.js module. When set, this path is used for Jering
21+
/// Node.js invocations instead of the default assembly-relative path. This is used when the RealtimeServer
22+
/// runs in a separate docker container where the module exists at a different location.
23+
/// </summary>
24+
public string? RealtimeServerModulePath { get; set; }
25+
1826
public DocConfig UserDoc { get; set; } = new DocConfig("users", typeof(User));
1927
public DocConfig ProjectDoc { get; set; }
2028

0 commit comments

Comments
 (0)