diff --git a/Dockerfile b/Dockerfile
index 9b09888..a413172 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -23,6 +23,7 @@ RUN apt-get clean \
&& apt-get -y update \
&& apt install -y \
sudo \
+gosu \
net-tools \
fcitx-hangul \
fonts-nanum* \
diff --git a/README.md b/README.md
index ca68345..af75160 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,10 @@
| 이미지 태그 (Image Tag) | TensorFlow 버전 | CUDA / cuDNN | 베이스 OS (Base OS) | 주요 변경사항 및 설명 |
| :--- | :--- | :--- | :--- | :--- |
-| `dguailab/decs:latest`
`dguailab/decs:251002` | **2.18.0** | CUDA 12.5
cuDNN 8.9 | Ubuntu 22.04 | **(최신)** TensorFlow 2.18.0 업그레이드, 최신 GPU 환경 지원 |
+| `dguailab/decs:latest`
`dguailab/decs:260403` | **2.18.0** | CUDA 12.5
cuDNN 8.9 | Ubuntu 22.04 | **(최신)** 계정/권한 검증 강화, Jupyter 비-root 실행, 의도되지 않은 MOTD 출력 방지 |
+| `dguailab/decs:260201` | **2.18.0** | CUDA 12.5
cuDNN 8.9 | Ubuntu 22.04 | **(이전 수정 버전)** 의도되지 않은 MOTD 출력 방지 버그 수정 |
+| `dguailab/decs:251023` | **2.18.0** | CUDA 12.5
cuDNN 8.9 | Ubuntu 22.04 | **(이전 안정 버전)** Jupyter Notebook 버전 변경으로 인한 오류 해결 버전 |
+| `dguailab/decs:251002` | **2.18.0** | CUDA 12.5
cuDNN 8.9 | Ubuntu 22.04 | TensorFlow 2.18.0 업그레이드, 최신 GPU 환경 지원 |
| `dguailab/decs:250926` | **2.13.0** | CUDA 11.8
cuDNN 8.6 | Ubuntu 20.04 | **(이전 안정 버전)** TensorFlow 2.13.0 기반의 안정화 버전 |
## ⚙️ 사용 방법
diff --git a/entrypoint.sh b/entrypoint.sh
index cb2113d..77b3d5c 100644
--- a/entrypoint.sh
+++ b/entrypoint.sh
@@ -1,54 +1,92 @@
#!/bin/bash
+set -euo pipefail
+
+: "${USER_ID:?USER_ID is required}"
+: "${TARGET_UID:?TARGET_UID is required}"
+
+USER_GROUP="${USER_GROUP:-$USER_ID}"
+TARGET_GID="${TARGET_GID:-$TARGET_UID}"
+USER_HOME="/home/$USER_ID"
+JUPYTER_DIR="$USER_HOME/decs_jupyter_lab"
+JUPYTER_CONFIG_DIR="$USER_HOME/.jupyter"
+JUPYTER_CONFIG_FILE="$JUPYTER_CONFIG_DIR/jupyter_notebook_config.py"
+
+ensure_account_matches_mounts() {
+ local passwd_entry
+ local group_entry
+ local actual_uid
+ local actual_gid
+ local actual_home
+ local actual_group_gid
+
+ passwd_entry="$(getent passwd "$USER_ID" || true)"
+ if [[ -z "$passwd_entry" ]]; then
+ echo "[ERROR] User '$USER_ID' not found in mounted /etc/passwd" >&2
+ exit 1
+ fi
-sudo apt update
-sudo apt install -y auditd
+ IFS=: read -r _ _ actual_uid actual_gid _ actual_home _ <<<"$passwd_entry"
-# /etc/audit/audit.rules 파일에 줄 추가
-echo "-a always,exit -F arch=b64 -S unlink -S unlinkat -S rename -S renameat -F auid=$USER_ID -k rm_commands" >> /etc/audit/audit.rules
+ if [[ "$actual_uid" != "$TARGET_UID" ]]; then
+ echo "[ERROR] USER_ID '$USER_ID' has uid '$actual_uid', expected '$TARGET_UID'" >&2
+ exit 1
+ fi
-# history 명령어 칠 때 명령어를 입력한 시간이 같이 나오게 하는 명령어
-echo 'HISTTIMEFORMAT="[%Y-%m-%d %H:%M:%S] "' >> /etc/profile
-echo 'export HISTTIMEFORMAT' >> /etc/profile
+ if [[ "$actual_gid" != "$TARGET_GID" ]]; then
+ echo "[ERROR] USER_ID '$USER_ID' has gid '$actual_gid', expected '$TARGET_GID'" >&2
+ exit 1
+ fi
-if ! id "$USER_ID" >/dev/null 2>&1; then
- # 유저 디렉토리 존재하지 않는 경우, 디렉토리와 skel 생성
- if [ ! -d "/home/$USER_ID/" ]; then
- cp -R /etc/skel/. "/home/$USER_ID"
- chmod -R 700 "/home/$USER_ID" # 초기 권한 설정 후 아래에서 변경
-
- # history -w 현재시간.txt파일을 만들고, /var/log/audit로 이동하는 부분임. 사용자가 로그아웃 할 때
- echo 'cd ~' >> /home/$USER_ID/.bash_logout
- echo 'current_time=$(date +%Y-%m-%d_%H-%M-%S)' >> /home/$USER_ID/.bash_logout
- echo 'history -w $current_time.txt' >> /home/$USER_ID/.bash_logout
- echo 'sudo mv $current_time.txt /var/log/audit/' >> /home/$USER_ID/.bash_logout
+ if [[ "$actual_home" != "$USER_HOME" ]]; then
+ echo "[ERROR] USER_ID '$USER_ID' has home '$actual_home', expected '$USER_HOME'" >&2
+ exit 1
fi
- useradd -s /bin/bash -d /home/$USER_ID -u $UID $USER_ID
- # sudo 권한 제공
- echo "$USER_ID ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
+ group_entry="$(getent group "$USER_GROUP" || true)"
+ if [[ -z "$group_entry" ]]; then
+ echo "[ERROR] Group '$USER_GROUP' not found in mounted /etc/group" >&2
+ exit 1
+ fi
- # 비밀번호 설정
- echo "$USER_ID:$USER_PW" | chpasswd
+ IFS=: read -r _ _ actual_group_gid _ <<<"$group_entry"
+ if [[ "$actual_group_gid" != "$TARGET_GID" ]]; then
+ echo "[ERROR] USER_GROUP '$USER_GROUP' has gid '$actual_group_gid', expected '$TARGET_GID'" >&2
+ exit 1
+ fi
+}
- # 서버관리자와 유저계정의 ssh 접속을 허용 및 다중접속 허용
- sed -i "/^#PermitRootLogin/a AllowUsers svmanager" /etc/ssh/sshd_config
- sed -i "/^#PermitRootLogin/a AllowUsers $USER_ID" /etc/ssh/sshd_config
- sed -i 's/^UsePAM yes/UsePAM no/' /etc/ssh/sshd_config
-fi
+ensure_sshd_allow_user() {
+ local user_name="$1"
+ if ! getent passwd "$user_name" >/dev/null 2>&1; then
+ echo "[WARN] Skipping AllowUsers for missing account '$user_name'" >&2
+ return 0
+ fi
+ if ! grep -qxF "AllowUsers $user_name" /etc/ssh/sshd_config; then
+ printf '\nAllowUsers %s\n' "$user_name" >> /etc/ssh/sshd_config
+ fi
+}
-# 그룹이 존재하지 않을 경우 생성하고 사용자를 그룹에 추가
-if ! getent group "$USER_GROUP" >/dev/null 2>&1; then
- groupadd -g $GID "$USER_GROUP"
-fi
-usermod -aG "$USER_GROUP" "$USER_ID"
+apt-get update
+apt-get install -y auditd
+
+# /etc/audit/audit.rules 파일에 줄 추가
+echo "-a always,exit -F arch=b64 -S unlink -S unlinkat -S rename -S renameat -F auid=$TARGET_UID -k rm_commands" >> /etc/audit/audit.rules
-# 사용자와 그룹이 모두 준비된 후, 소유권과 권한을 설정합니다.
-chown -R "$USER_ID:$USER_GROUP" "/home/$USER_ID"
-chmod 750 "/home/$USER_ID"
+# history 명령어 칠 때 명령어를 입력한 시간이 같이 나오게 하는 명령어
+echo 'HISTTIMEFORMAT="[%Y-%m-%d %H:%M:%S] "' >> /etc/profile
+echo 'export HISTTIMEFORMAT' >> /etc/profile
+
+ensure_account_matches_mounts
+# admin_infra가 주입한 account files를 기준으로, writable path만 준비한다.
+mkdir -p "$USER_HOME"
+chown "$TARGET_UID:$TARGET_GID" "$USER_HOME"
+chmod 750 "$USER_HOME"
# MOTD 공지 출력하도록 설정
sed -i 's/^#\?UsePAM .*/UsePAM yes/' /etc/ssh/sshd_config
+ensure_sshd_allow_user "svmanager"
+ensure_sshd_allow_user "$USER_ID"
cat < /etc/default/motd-news
ENABLED=1
echo "\e[0;33m
@@ -64,6 +102,9 @@ resulting from failing to check and respond within 24 hours of a Slack notice.
\e[0m"
EOF
+# 의도되지 않은 MOTD 출력 방지
+sed -i.bak '/^[[:space:]]*else[[:space:]]*$/,/^[[:space:]]*EXPL[[:space:]]*$/d' /etc/bash.bashrc
+
for file in /etc/update-motd.d/60-unminimize /etc/update-motd.d/10-help-text; do
if [[ -f "$file" ]]; then
sed -i '/^[^#]/ s/^/#/' "$file"
@@ -79,40 +120,32 @@ fi
service ssh restart
# jupyter lab 에서 생성한 ipynb 파일을 저장할 디렉토리 생성 (없는경우만 신규 생성)
-if [ ! -d "/home/$USER_ID/decs_jupyter_lab" ]; then
- mkdir /home/$USER_ID/decs_jupyter_lab
- echo "Created /home/$USER_ID/decs_jupyter_lab dir...."
-fi
+mkdir -p "$JUPYTER_DIR" "$JUPYTER_CONFIG_DIR"
+chown -R "$TARGET_UID:$TARGET_GID" "$JUPYTER_DIR" "$JUPYTER_CONFIG_DIR"
-# jupyter lab config 파일이 없으면 생성
-mkdir -p /home/$USER_ID/.jupyter/
-
-if [ ! -f /home/$USER_ID/.jupyter/jupyter_notebook_config.py ]; then
- echo "jupyter_notebook_config.py not found, generating..."
- /opt/anaconda3/bin/jupyter notebook --generate-config
- cp /root/.jupyter/jupyter_notebook_config.py /home/$USER_ID/.jupyter/
+if [ ! -f "$JUPYTER_CONFIG_FILE" ]; then
+ echo "jupyter_notebook_config.py not found, generating..."
+ gosu "$USER_ID:$USER_GROUP" /opt/anaconda3/bin/jupyter notebook --generate-config --config="$JUPYTER_CONFIG_FILE"
else
- echo "jupyter_notebook_config.py already exists."
+ echo "jupyter_notebook_config.py already exists."
fi
# jupyter lab 접속 설정
-sed -i "1i c.JupyterApp.config_file_name = 'jupyter_notebook_config.py'\nc.NotebookApp.allow_origin = '*'\nc.NotebookApp.ip = '0.0.0.0'\nc.NotebookApp.open_browser = False\nc.NotebookApp.allow_remote_access = True\nc.NotebookApp.allow_root = True\nc.NotebookApp.notebook_dir='/home/$USER_ID/decs_jupyter_lab'" /home/$USER_ID/.jupyter/jupyter_notebook_config.py
-
-# Jupyter Lab 토큰을 랜덤 문자열로 생성하고 저장
-TOKEN=$(tr -dc A-Za-z0-9 /home/$USER_ID/decs_jupyter_lab/jupyter_token.txt
-chmod 600 /home/$USER_ID/decs_jupyter_lab/jupyter_token.txt
-chown $USER_ID:$USER_ID /home/$USER_ID/decs_jupyter_lab/jupyter_token.txt
-
-# jupyter_lab 기동
-echo "trying jupyter lab..."
-nohup /opt/anaconda3/bin/jupyter lab --NotebookApp.token=$TOKEN --config=/home/$USER_ID/.jupyter/jupyter_notebook_config.py >/dev/null 2>&1 &
-echo "jupyter lab listening!"
+sed -i "1i c.JupyterApp.config_file_name = 'jupyter_notebook_config.py'\nc.NotebookApp.allow_origin = '*'\nc.NotebookApp.ip = '0.0.0.0'\nc.NotebookApp.open_browser = False\nc.NotebookApp.allow_remote_access = True\nc.NotebookApp.allow_root = False\nc.NotebookApp.notebook_dir='$JUPYTER_DIR'" "$JUPYTER_CONFIG_FILE"
+chown "$TARGET_UID:$TARGET_GID" "$JUPYTER_CONFIG_FILE"
# ldconfig permission 오류 방지
# bash.bashrc에서 ldconfig 명령어 삭제 후 명령어 실행 및 결과 출력
sed -i '/ldconfig/d' /etc/bash.bashrc
ldconfig && echo "ldconfig executed successfully" || echo "ldconfig failed"
-#entrypoint.sh 를 실행하고 나서 컨테이너가 Exit 하지 않게함
-tail -F /dev/null
+# Jupyter 실행과 컨테이너 유지는 비-root 사용자로 전환한다.
+exec gosu "$USER_ID:$USER_GROUP" bash -lc '
+TOKEN=$(tr -dc A-Za-z0-9 "'"$JUPYTER_DIR"'/jupyter_token.txt"
+chmod 600 "'"$JUPYTER_DIR"'/jupyter_token.txt"
+echo "trying jupyter lab..."
+nohup /opt/anaconda3/bin/jupyter lab --NotebookApp.token="$TOKEN" --config="'"$JUPYTER_CONFIG_FILE"'" >/dev/null 2>&1 &
+echo "jupyter lab listening!"
+exec tail -F /dev/null
+'