Skip to content
Merged
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
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ RUN apt-get clean \
&& apt-get -y update \
&& apt install -y \
sudo \
gosu \
net-tools \
fcitx-hangul \
fonts-nanum* \
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@

| 이미지 태그 (Image Tag) | TensorFlow 버전 | CUDA / cuDNN | 베이스 OS (Base OS) | 주요 변경사항 및 설명 |
| :--- | :--- | :--- | :--- | :--- |
| `dguailab/decs:latest`<br>`dguailab/decs:251002` | **2.18.0** | CUDA 12.5<br>cuDNN 8.9 | Ubuntu 22.04 | **(최신)** TensorFlow 2.18.0 업그레이드, 최신 GPU 환경 지원 |
| `dguailab/decs:latest`<br>`dguailab/decs:260403` | **2.18.0** | CUDA 12.5<br>cuDNN 8.9 | Ubuntu 22.04 | **(최신)** 계정/권한 검증 강화, Jupyter 비-root 실행, 의도되지 않은 MOTD 출력 방지 |
| `dguailab/decs:260201` | **2.18.0** | CUDA 12.5<br>cuDNN 8.9 | Ubuntu 22.04 | **(이전 수정 버전)** 의도되지 않은 MOTD 출력 방지 버그 수정 |
| `dguailab/decs:251023` | **2.18.0** | CUDA 12.5<br>cuDNN 8.9 | Ubuntu 22.04 | **(이전 안정 버전)** Jupyter Notebook 버전 변경으로 인한 오류 해결 버전 |
| `dguailab/decs:251002` | **2.18.0** | CUDA 12.5<br>cuDNN 8.9 | Ubuntu 22.04 | TensorFlow 2.18.0 업그레이드, 최신 GPU 환경 지원 |
| `dguailab/decs:250926` | **2.13.0** | CUDA 11.8<br>cuDNN 8.6 | Ubuntu 20.04 | **(이전 안정 버전)** TensorFlow 2.13.0 기반의 안정화 버전 |

## ⚙️ 사용 방법
Expand Down
157 changes: 95 additions & 62 deletions entrypoint.sh
Original file line number Diff line number Diff line change
@@ -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
Comment on lines +60 to +66

Copilot AI Apr 2, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ensure_sshd_allow_user only checks for an exact line match (AllowUsers <user>). If sshd_config already has AllowUsers with multiple users/patterns on one line, this will append a duplicate directive and can lead to confusing/incorrect allowlists. Consider parsing/updating an existing AllowUsers line (or using a single consolidated directive) instead of only appending exact matches.

Suggested change
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
local sshd_config="/etc/ssh/sshd_config"
local tmp_config
if ! getent passwd "$user_name" >/dev/null 2>&1; then
echo "[WARN] Skipping AllowUsers for missing account '$user_name'" >&2
return 0
fi
if awk -v user="$user_name" '
/^[[:space:]]*#/ { next }
/^[[:space:]]*AllowUsers([[:space:]]|$)/ {
for (i = 2; i <= NF; i++) {
if ($i == user) {
found = 1
exit
}
}
}
END { exit(found ? 0 : 1) }
' "$sshd_config"; then
return 0
fi
if awk '
/^[[:space:]]*#/ { next }
/^[[:space:]]*AllowUsers([[:space:]]|$)/ { found = 1; exit }
END { exit(found ? 0 : 1) }
' "$sshd_config"; then
tmp_config="$(mktemp)"
awk -v user="$user_name" '
!updated && /^[[:space:]]*AllowUsers([[:space:]]|$)/ {
print $0 " " user
updated = 1
next
}
{ print }
' "$sshd_config" > "$tmp_config"
cat "$tmp_config" > "$sshd_config"
rm -f "$tmp_config"
else
printf '\nAllowUsers %s\n' "$user_name" >> "$sshd_config"
fi

Copilot uses AI. Check for mistakes.
}

# 그룹이 존재하지 않을 경우 생성하고 사용자를 그룹에 추가
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
Comment on lines +69 to +70

Copilot AI Apr 2, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

apt-get update/apt-get install is being run at container startup. This makes startup slower and can fail in restricted/offline environments, and it also makes the runtime behavior depend on the state of apt repos at run time. Prefer installing auditd in the Dockerfile during image build (and remove these lines from the entrypoint).

Suggested change
apt-get update
apt-get install -y auditd
if ! command -v auditd >/dev/null 2>&1; then
echo "[ERROR] Required package 'auditd' is not installed in the image. Install it during image build instead of at container startup." >&2
exit 1
fi

Copilot uses AI. Check for mistakes.

# /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
Comment on lines +72 to +77

Copilot AI Apr 2, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These echo >> writes are not idempotent: every container start will append another identical audit rule and another HISTTIMEFORMAT export to /etc/profile, causing unbounded duplication. Add a guard (e.g., grep -q for an existing line / key) before appending, or write the files in an overwrite/managed-block way.

Copilot uses AI. Check for mistakes.

ensure_account_matches_mounts

Comment on lines +79 to 80

Copilot AI Apr 2, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ensure_account_matches_mounts is invoked after apt-get update/install and other system mutations. To fail fast (and avoid doing network/package work when UID/GID mismatch will immediately exit), run the account/UID/GID validation as early as possible before any installs/file modifications.

Copilot uses AI. Check for mistakes.
# 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 <<EOF > /etc/default/motd-news
ENABLED=1
echo "\e[0;33m
Expand All @@ -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"
Expand All @@ -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 </dev/urandom | head -c 10)
echo "$TOKEN" > /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"

Copilot AI Apr 2, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sed -i "1i ..." prepends the same config block on every container start, even when the config file already exists, which will quickly corrupt the config with repeated duplicated settings. Make this update idempotent (e.g., check for a sentinel line before inserting, or manage the file via a template/overwrite approach).

Suggested change
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"
if ! grep -Fqx "c.JupyterApp.config_file_name = 'jupyter_notebook_config.py'" "$JUPYTER_CONFIG_FILE"; then
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"
else
echo "Jupyter config settings already present; skipping update."
fi

Copilot uses AI. Check for mistakes.
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 </dev/urandom | head -c 10)
echo "$TOKEN" > "'"$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
'
Loading