Skip to content
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
25 changes: 23 additions & 2 deletions 03_container_layer/docker/admin/rocketchat/.env.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
# ── Network ───────────────────────────────────────────────────────────────────
RC_HTTP_PORT=3000
RC_HOSTNAME=localhost

# Public URL that Rocket.Chat advertises in links and e-mails.
RC_BASE_URL=http://localhost:3000
RC_ADMIN_USER=rc-admin

# ── Admin credentials ─────────────────────────────────────────────────────────
# The primary admin is created automatically by the official Rocket.Chat image
# via ADMIN_USERNAME / ADMIN_PASS at first boot.
RC_ADMIN_USER=admin
RC_ADMIN_PASS=Admin1234!
RC_ADMIN_EMAIL=admin@range42.local
HTTP_PORT=3000

# ── Teams ─────────────────────────────────────────────────────────────────────
# Comma-separated list of player team names. One lead (admin) + N regular users
# are created per entry.
RC_TEAMS=team-blue,team-red
# Name used as the username prefix for instructor accounts.
RC_INSTRUCTOR_ORG=instructors
# Number of instructor accounts to create.
RC_INSTRUCTOR_COUNT=1
# Number of regular (non-lead) player accounts per team.
RC_USERS_PER_TEAM=2
# Email domain used for all generated team and instructor accounts.
RC_USER_DOMAIN=range42.local
28 changes: 6 additions & 22 deletions 03_container_layer/docker/admin/rocketchat/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,28 +1,12 @@
#
# ISSUE 147
#

#
# BUILDER — node:20-alpine (same base as official Rocket.Chat Dockerfile)
#
FROM node:20-alpine AS builder

RUN apk add --no-cache jq && \
wget -q https://github.com/mikefarah/yq/releases/download/v4.44.1/yq_linux_amd64 \
-O /usr/bin/yq && chmod +x /usr/bin/yq

COPY provisioning/ /provisioning/
RUN chmod +x /provisioning/init.sh

# Lightweight provisioner image — Alpine with the REST-API toolchain.
# Provisioning scripts are volume-mounted at runtime (not baked in),
# mirroring the misp-standalone provisioner pattern.
#
# RUNTIME — rocketchat/rocket.chat:latest (Alpine/Node-based)
#
FROM rocketchat/rocket.chat:latest AS runtime

USER root
FROM alpine:3.20

COPY --from=builder /usr/bin/yq /usr/bin/yq
COPY --from=builder /usr/bin/jq /usr/bin/jq
COPY --from=builder /provisioning/ /provisioning/
RUN apk add --no-cache bash curl jq openssl

ENTRYPOINT ["/provisioning/init.sh"]
ENTRYPOINT ["/provisioning/provision.sh"]
8 changes: 6 additions & 2 deletions 03_container_layer/docker/admin/rocketchat/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ SERVICE = rocketchat
PROVISIONER = rocketchat-provisioner
DEBUG_SERVICE = $(SERVICE)-debug

.PHONY: help up down stop stop-debug-build build rebuild build-up rebuild-up tokens reprovision term term-debug-build clean print
.PHONY: help up down stop stop-debug-build build rebuild build-up rebuild-up tokens keys reprovision term term-debug-build clean print

help:
@echo ""
Expand All @@ -24,7 +24,8 @@ help:
@echo " make rebuild - full rebuild $(SERVICE) without cache"
@echo " make rebuild-up - full rebuild $(SERVICE) without cache then run"
@echo ""
@echo " make tokens - print generated personal access tokens"
@echo " make tokens - print generated personal access tokens (username:PAT)"
@echo " make keys - print full credentials JSON (all users + passwords)"
@echo " make term - open shell in $(SERVICE) container"
@echo " make term-debug-build - build and get term on $(DEBUG_SERVICE) "
@echo " make clean - delete all containers, images, volumes and unused network"
Expand Down Expand Up @@ -70,6 +71,9 @@ rebuild-up:
tokens:
docker run --rm -v rocketchat-tokens:/tokens alpine cat /tokens/tokens.txt

keys:
docker run --rm -v rocketchat-tokens:/tokens alpine cat /tokens/rc-credentials.json

reprovision:
docker run --rm -v rocketchat-tokens:/tokens alpine rm -f /tokens/.provisioned
docker compose up $(PROVISIONER)
Expand Down
168 changes: 100 additions & 68 deletions 03_container_layer/docker/admin/rocketchat/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Rocket.Chat — Standalone Docker Deployment

Issue 147. Dockerized Rocket.Chat with MongoDB replica set and automated user / personal-access-token provisioning.
Issue 147. Dockerized Rocket.Chat with MongoDB replica set and team-aware automated user and Personal Access Token provisioning.

---

Expand All @@ -24,124 +24,151 @@ $EDITOR .env
# 2. Build and start the full stack
make build-up

# 3. Wait for the provisioner to finish, then check tokens
make tokens
# 3. Wait for the provisioner to finish (~2 min on first boot), then check tokens
make tokens # username:PAT pairs
make keys # full credentials JSON
```

> **MongoDB replica set** initialises automatically via the `mongo-init-replica` one-shot container. No manual `rs.initiate()` step is needed.

The web UI is available at `http://localhost:3000` (or `RC_BASE_URL`).
Default admin credentials: `rc-admin` / `Admin1234!` (change in `.env`).
Default admin credentials: `admin` / `Admin1234!` (change in `.env`).

---

## Build & Push

```sh
# Build provisioner image
make build
## Provisioning Architecture

# Full rebuild without cache
make rebuild
User provisioning is handled by a dedicated one-shot `provisioner` service (Alpine + bash/curl/jq/openssl). Scripts are **volume-mounted at runtime** — not baked into the image — so they can be updated without a rebuild.

# Tag and push (adjust registry as needed)
docker tag rocketchat-provisioner registry.example.com/range42/rocketchat-provisioner:latest
docker push registry.example.com/range42/rocketchat-provisioner:latest
```
provision.sh ← orchestrator, mounted as ENTRYPOINT
└─ provision-users.sh creates all user accounts, writes /tokens/rc-credentials.json
└─ provision-tokens.sh generates PATs for every user, writes /tokens/tokens.txt
```

---

## Declaring Users

Edit `provisioning/users.yml` before first deployment:
Provisioning is **idempotent**: a `/tokens/.provisioned` stamp prevents re-running on container restart. To re-provision, run `make reprovision`.

```yaml
# !! CHANGE ALL PASSWORDS BEFORE DEPLOYING !!
### What gets created

admins:
- username: rc-admin2
email: admin2@range42.local
password: "Admin1234!"
name: "RC Admin 2"
Given the default `.env.example` values (`RC_TEAMS=team-blue,team-red`, `RC_INSTRUCTOR_COUNT=1`, `RC_USERS_PER_TEAM=2`):

users:
- username: trainee01
email: trainee01@range42.local
password: "Trainee1234!"
name: "Trainee 01"
```
| Username | Role | Team |
|---|---|---|
| `admin` | admin | admin |
| `instructors` | admin | instructors |
| `team-blue-lead` | admin | team-blue |
| `team-blue-user1` | user | team-blue |
| `team-blue-user2` | user | team-blue |
| `team-red-lead` | admin | team-red |
| `team-red-user1` | user | team-red |
| `team-red-user2` | user | team-red |

The primary admin (`rc-admin`) is created automatically via the `ADMIN_USERNAME` environment variable. The users listed in `users.yml` are **additional** accounts.
All passwords are auto-generated (20-char, `R42!` prefix + `openssl rand`). Retrieve them via `make keys`.

---

## Token Retrieval

Personal access tokens are written to `/tokens/tokens.txt` (inside the `rocketchat-tokens` volume) at the end of provisioning.
Two output files are written to the `rocketchat-tokens` named volume:

```sh
# Print all tokens
# Personal Access Tokens (username:PAT, one per line)
make tokens

# Or read directly from the volume
docker run --rm -v rocketchat_rocketchat-tokens:/tokens:ro busybox cat /tokens/tokens.txt
# Full credentials JSON (usernames, passwords, roles, teams)
make keys
```

Each line has the format:
`tokens.txt` format:
```
admin:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
team-blue-lead:yyyyyyyyyyyyyyyyyyyyyyyyyyyy
```

`rc-credentials.json` format:
```json
{
"service": "rocketchat",
"baseurl": "http://localhost:3000",
"users": [
{"username": "admin", "role": "admin", "team": "admin", "password": "Admin1234!"},
{"username": "team-blue-lead", "role": "admin", "team": "team-blue", "password": "R42!..."},
{"username": "team-blue-user1","role": "user", "team": "team-blue", "password": "R42!..."}
]
}
```
username:personalAccessToken

Both files are `chmod 600` inside the volume.

---

## Build & Push

```sh
make build # build provisioner image
make rebuild # full rebuild without cache

# Tag and push (adjust registry as needed)
docker tag rocketchat-provisioner registry.example.com/range42/rocketchat-provisioner:latest
docker push registry.example.com/range42/rocketchat-provisioner:latest
```

---

## Environment Variables

| Variable | Default | Description |
|---|---|---|
| `RC_HTTP_PORT` | `3000` | Host port mapped to Rocket.Chat |
| `RC_HOSTNAME` | `localhost` | Hostname for URL construction |
| `RC_BASE_URL` | `http://localhost:3000` | Public URL (`ROOT_URL` in Rocket.Chat) |
| `RC_ADMIN_USER` | `admin` | Bootstrap admin username |
| `RC_ADMIN_PASS` | `Admin1234!` | Bootstrap admin password |
| `RC_ADMIN_EMAIL` | `admin@range42.local` | Bootstrap admin email |
| `RC_TEAMS` | `team-blue,team-red` | Comma-separated team names |
| `RC_INSTRUCTOR_ORG` | `instructors` | Username prefix for instructor accounts |
| `RC_INSTRUCTOR_COUNT` | `1` | Number of instructor accounts to create |
| `RC_USERS_PER_TEAM` | `2` | Regular (non-lead) users per team |
| `RC_USER_DOMAIN` | `range42.local` | Email domain for generated accounts |

---

## API Usage Examples

```sh
# Get server info (no auth needed)
curl http://localhost:3000/api/v1/info

# List channels (authenticated)
TOKEN="<paste token from tokens.txt>"
USER_ID="<userId — retrieve via login endpoint>"
# Authenticate and store credentials
LOGIN=$(curl -sf -X POST http://localhost:3000/api/v1/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"Admin1234!"}')
USER_ID=$(echo "${LOGIN}" | jq -r '.data.userId')
AUTH_TOKEN=$(echo "${LOGIN}" | jq -r '.data.authToken')

curl -X GET http://localhost:3000/api/v1/channels.list \
-H "X-Auth-Token: ${TOKEN}" \
# List channels
curl http://localhost:3000/api/v1/channels.list \
-H "X-Auth-Token: ${AUTH_TOKEN}" \
-H "X-User-Id: ${USER_ID}"

# Post a message
curl -X POST http://localhost:3000/api/v1/chat.postMessage \
-H "X-Auth-Token: ${TOKEN}" \
-H "X-Auth-Token: ${AUTH_TOKEN}" \
-H "X-User-Id: ${USER_ID}" \
-H "Content-Type: application/json" \
-d '{"channel":"#general","text":"Hello from range42!"}'
```

---

## Environment Variables

| Variable | Default | Description |
|---|---|---|
| `RC_BASE_URL` | `http://localhost:3000` | Public URL (used by Rocket.Chat as `ROOT_URL`) |
| `RC_ADMIN_USER` | `rc-admin` | Initial admin username |
| `RC_ADMIN_PASS` | `Admin1234!` | Initial admin password |
| `RC_ADMIN_EMAIL` | `admin@range42.local` | Initial admin email |
| `HTTP_PORT` | `3000` | Host port mapped to Rocket.Chat |

---

## Troubleshooting

### MongoDB replica set issues

**Symptom:** Rocket.Chat exits with `MongoServerError: not primary`.

**Cause:** The replica set has not been initialised yet. The `mongo-init-replica` container handles this automatically, but it requires MongoDB to be healthy first.

**Fix:**
```sh
# Check mongo-init-replica logs
docker logs rocketchat-mongo-init

# Force re-init manually if needed
Expand All @@ -151,19 +178,24 @@ docker exec rocketchat-mongodb mongosh --eval \

### Provisioner exits with auth error

**Symptom:** `[init] ERROR: Failed to authenticate as admin.`
**Symptom:** `[provision-users] ERROR: Failed to authenticate as admin.`

**Cause:** Rocket.Chat is not yet fully ready (it can take 60–90 s on first boot).
**Cause:** Rocket.Chat is not yet fully ready (can take 60–90 s on first boot). The provisioner waits up to 180 s then exits with an error.

**Fix:** The provisioner will be restarted automatically by Docker Compose if `restart: "no"` is overridden, or you can re-run it manually:
**Fix:** Re-run the provisioner:
```sh
docker compose run --rm provisioner
make reprovision
```

### Tokens already exist
### Re-provisioning

If the provisioner runs a second time, `generatePersonalAccessToken` will fail for tokens named `api-token` that already exist. This is handled gracefully — a warning is printed and the existing token is left in place. Re-provision with a clean volume to regenerate:
Remove the idempotency stamp and re-run:
```sh
docker volume rm rocketchat_rocketchat-tokens
docker compose run --rm provisioner
make reprovision
```

To completely reset users and tokens (start fresh):
```sh
docker compose down -v
make build-up
```
Loading