commit 7320b8da8dcc13565ebeb485e2ee825ef71577a5 Author: Claude Sonnet 5 Date: Wed Aug 12 19:23:57 2026 +0000 Restore from Gitea ZIP snapshot (12.08.2026) after full instance reinstall Git history was lost when the previous Gitea instance was wiped and reinstalled due to an unresolved corruption bug — this commit is the last known-good file content, exported before the reinstall. Prior commit history is not recoverable through this path. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cb3d533 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +*.retry +*.log +.env +*.swp +.DS_Store +__pycache__/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..260f9aa --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +# osTicket Ansible Playbook for Debian 13 + +Автоматическая установка osTicket. + +## Переменные для Semaphore + +- `MARIADB_ROOT_PASSWORD` - пароль root MySQL +- `APP_DB_PASSWORD` - пароль пользователя osTicket +- `DOMAIN_NAME` - домен или IP сервера diff --git a/deploy.yml b/deploy.yml new file mode 100644 index 0000000..87f4aff --- /dev/null +++ b/deploy.yml @@ -0,0 +1,13 @@ +--- +- name: Полная установка osTicket с правильными правами + hosts: all + become: yes + gather_facts: yes + vars: + ansible_become_method: sudo + + roles: + - apache + - php + - mariadb + - osticket diff --git a/enable-root-ssh.yml b/enable-root-ssh.yml new file mode 100644 index 0000000..06c706d --- /dev/null +++ b/enable-root-ssh.yml @@ -0,0 +1,33 @@ +--- +- name: Включение root доступа по SSH + hosts: all + become: yes + gather_facts: yes + + tasks: + - name: Разрешить root логин через SSH + ansible.builtin.lineinfile: + path: /etc/ssh/sshd_config + regexp: '^#?PermitRootLogin' + line: 'PermitRootLogin yes' + backup: yes + notify: Restart SSH + + - name: Разрешить аутентификацию по паролю для root (если нужно) + ansible.builtin.lineinfile: + path: /etc/ssh/sshd_config + regexp: '^#?PasswordAuthentication' + line: 'PasswordAuthentication yes' + notify: Restart SSH + + - name: Убедиться, что SSH сервис запущен + ansible.builtin.systemd: + name: ssh + state: started + enabled: yes + + handlers: + - name: Restart SSH + ansible.builtin.systemd: + name: ssh + state: restarted diff --git a/group_vars/all.yml b/group_vars/all.yml new file mode 100644 index 0000000..b689a95 --- /dev/null +++ b/group_vars/all.yml @@ -0,0 +1,6 @@ +# Переменные переопределяются в Semaphore через Environment Variables +mariadb_root_password: "{{ lookup('env', 'MARIADB_ROOT_PASSWORD') | default('CHANGE_ME', true) }}" +app_db_name: "osticket" +app_db_user: "osticket_user" +app_db_password: "{{ lookup('env', 'APP_DB_PASSWORD') | default('CHANGE_ME', true) }}" +domain_name: "{{ lookup('env', 'DOMAIN_NAME') | default('CHANGE_ME', true) }}" diff --git a/install.yml b/install.yml new file mode 100644 index 0000000..ee4943c --- /dev/null +++ b/install.yml @@ -0,0 +1,12 @@ +--- +- name: Полная установка osTicket + hosts: all + become: yes + become_method: sudo + gather_facts: yes + + roles: + - apache + - php + - mariadb + - osticket diff --git a/osTicket.zip b/osTicket.zip new file mode 100644 index 0000000..e2e8594 Binary files /dev/null and b/osTicket.zip differ diff --git a/reset-mariadb-password.yml b/reset-mariadb-password.yml new file mode 100644 index 0000000..3095998 --- /dev/null +++ b/reset-mariadb-password.yml @@ -0,0 +1,48 @@ +--- +- name: Сброс пароля root MariaDB + hosts: all + become: yes + gather_facts: yes + + tasks: + - name: Stop MariaDB service + ansible.builtin.systemd: + name: mariadb + state: stopped + + - name: Start MariaDB in safe mode with skip-grant-tables + ansible.builtin.shell: | + mysqld_safe --skip-grant-tables & + sleep 5 + async: 10 + poll: 0 + + - name: Reset root password + ansible.builtin.shell: | + mysql -u root << EOF + FLUSH PRIVILEGES; + ALTER USER 'root'@'localhost' IDENTIFIED BY '{{ mariadb_root_password }}'; + FLUSH PRIVILEGES; + EOF + + - name: Stop MariaDB safe mode + ansible.builtin.systemd: + name: mariadb + state: stopped + + - name: Start MariaDB normally + ansible.builtin.systemd: + name: mariadb + state: started + enabled: yes + + - name: Create .my.cnf file for root user + ansible.builtin.copy: + content: | + [client] + user=root + password={{ mariadb_root_password }} + dest: /root/.my.cnf + mode: '0600' + owner: root + group: root diff --git a/roles/apache/handlers/main.yml b/roles/apache/handlers/main.yml new file mode 100644 index 0000000..568ed00 --- /dev/null +++ b/roles/apache/handlers/main.yml @@ -0,0 +1,5 @@ +--- +- name: Restart Apache + ansible.builtin.systemd: + name: apache2 + state: restarted diff --git a/roles/apache/tasks/main.yml b/roles/apache/tasks/main.yml new file mode 100644 index 0000000..174c6bd --- /dev/null +++ b/roles/apache/tasks/main.yml @@ -0,0 +1,49 @@ +--- +- name: Update apt cache + ansible.builtin.apt: + update_cache: yes + cache_valid_time: 3600 + +- name: Install Apache + ansible.builtin.apt: + name: apache2 + state: present + +- name: Enable mod_rewrite + ansible.builtin.command: + cmd: a2enmod rewrite + creates: /etc/apache2/mods-enabled/rewrite.load + notify: Restart Apache + +- name: Create website root directory + ansible.builtin.file: + path: "/var/www/{{ domain_name }}" + state: directory + owner: www-data + group: www-data + mode: '0755' + +- name: Configure virtual host + ansible.builtin.template: + src: vhost.conf.j2 + dest: "/etc/apache2/sites-available/{{ domain_name }}.conf" + mode: '0644' + notify: Restart Apache + +- name: Enable virtual host + ansible.builtin.command: + cmd: "a2ensite {{ domain_name }}.conf" + creates: "/etc/apache2/sites-enabled/{{ domain_name }}.conf" + notify: Restart Apache + +- name: Disable default site + ansible.builtin.command: + cmd: a2dissite 000-default.conf + ignore_errors: yes + notify: Restart Apache + +- name: Start and enable Apache + ansible.builtin.systemd: + name: apache2 + state: started + enabled: yes diff --git a/roles/apache/templates/vhost.conf.j2 b/roles/apache/templates/vhost.conf.j2 new file mode 100644 index 0000000..d8dd95f --- /dev/null +++ b/roles/apache/templates/vhost.conf.j2 @@ -0,0 +1,13 @@ + + ServerName {{ domain_name }} + DocumentRoot /var/www/{{ domain_name }} + + + Options Indexes FollowSymLinks + AllowOverride All + Require all granted + + + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log combined + diff --git a/roles/mariadb/defaults/main.yml b/roles/mariadb/defaults/main.yml new file mode 100644 index 0000000..c1036a0 --- /dev/null +++ b/roles/mariadb/defaults/main.yml @@ -0,0 +1,5 @@ +--- +mariadb_root_password: "CHANGE_ME" +app_db_name: "CHANGE_ME" +app_db_user: "CHANGE_ME" +app_db_password: "CHANGE_ME" diff --git a/roles/mariadb/handlers/main.yml b/roles/mariadb/handlers/main.yml new file mode 100644 index 0000000..6f6b1a0 --- /dev/null +++ b/roles/mariadb/handlers/main.yml @@ -0,0 +1,5 @@ +--- +- name: Restart MariaDB + ansible.builtin.systemd: + name: mariadb + state: restarted diff --git a/roles/mariadb/tasks/main.yml b/roles/mariadb/tasks/main.yml new file mode 100644 index 0000000..5747750 --- /dev/null +++ b/roles/mariadb/tasks/main.yml @@ -0,0 +1,113 @@ +--- +- name: Check if MariaDB is already installed + ansible.builtin.command: dpkg -l mariadb-server + register: mariadb_installed + failed_when: false + changed_when: false + +- name: Check if MariaDB is running + ansible.builtin.systemd: + name: mariadb + register: mariadb_status + failed_when: false + changed_when: false + +- name: Install MariaDB server (if not installed) + block: + - name: Install MariaDB packages + ansible.builtin.apt: + name: + - mariadb-server + - mariadb-client + - python3-pymysql + state: present + update_cache: yes + + - name: Start and enable MariaDB + ansible.builtin.systemd: + name: mariadb + state: started + enabled: yes + when: mariadb_installed.rc != 0 + +- name: Check current root authentication method + ansible.builtin.shell: | + sudo mysql -e "SELECT plugin FROM mysql.user WHERE user='root' AND host='localhost';" -s -N + register: root_plugin + changed_when: false + ignore_errors: yes + +- name: Switch root authentication from unix_socket to password + ansible.builtin.shell: | + sudo mysql -e "ALTER USER 'root'@'localhost' IDENTIFIED BY '{{ mariadb_root_password }}';" + sudo mysql -e "ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '{{ mariadb_root_password }}';" + sudo mysql -e "FLUSH PRIVILEGES;" + when: root_plugin.stdout == 'unix_socket' + changed_when: true + +- name: Create .my.cnf for root user + ansible.builtin.copy: + content: | + [client] + user=root + password={{ mariadb_root_password }} + dest: /root/.my.cnf + mode: '0600' + owner: root + group: root + +- name: Test MySQL connection with password + ansible.builtin.command: mysql -u root -p{{ mariadb_root_password }} -e "SELECT 1" + register: mysql_test + changed_when: false + ignore_errors: yes + +- name: Remove anonymous users + community.mysql.mysql_user: + name: "" + host_all: yes + login_user: root + login_password: "{{ mariadb_root_password }}" + state: absent + ignore_errors: yes + +- name: Remove test database + community.mysql.mysql_db: + name: test + login_user: root + login_password: "{{ mariadb_root_password }}" + state: absent + ignore_errors: yes + +- name: Check if database exists + ansible.builtin.command: mysql -u root -p{{ mariadb_root_password }} -e "USE {{ app_db_name }}" + register: db_exists + failed_when: false + changed_when: false + ignore_errors: yes + +- name: Create osTicket database (if not exists) + community.mysql.mysql_db: + name: "{{ app_db_name }}" + login_user: root + login_password: "{{ mariadb_root_password }}" + state: present + when: db_exists.rc != 0 + +- name: Check if user exists + ansible.builtin.command: mysql -u root -p{{ mariadb_root_password }} -e "SELECT User FROM mysql.user WHERE User='{{ app_db_user }}'" + register: user_exists + failed_when: false + changed_when: false + ignore_errors: yes + +- name: Create osTicket database user (if not exists) + community.mysql.mysql_user: + name: "{{ app_db_user }}" + password: "{{ app_db_password }}" + priv: "{{ app_db_name }}.*:ALL" + host: localhost + login_user: root + login_password: "{{ mariadb_root_password }}" + state: present + when: user_exists.rc != 0 diff --git a/roles/osticket/tasks/main.yml b/roles/osticket/tasks/main.yml new file mode 100644 index 0000000..193caa6 --- /dev/null +++ b/roles/osticket/tasks/main.yml @@ -0,0 +1,102 @@ +--- +- name: Install unzip + ansible.builtin.apt: + name: unzip + state: present + +- name: Copy local osTicket ZIP to remote server + ansible.builtin.copy: + src: "{{ playbook_dir }}/osTicket.zip" + dest: /tmp/osticket.zip + mode: '0644' + +- name: Create temp extraction directory + ansible.builtin.file: + path: /tmp/osticket_extract + state: directory + mode: '0755' + +- name: Extract osTicket ZIP to temp directory + ansible.builtin.unarchive: + src: /tmp/osticket.zip + dest: /tmp/osticket_extract + remote_src: yes + +- name: Find upload directory in extracted files + ansible.builtin.find: + paths: /tmp/osticket_extract + patterns: "upload" + file_type: directory + register: upload_dir + +- name: Debug - show found upload directory + ansible.builtin.debug: + msg: "Upload directory found at: {{ upload_dir.files[0].path if upload_dir.files else 'NOT FOUND' }}" + +- name: Copy files to webroot (if upload directory found) + ansible.builtin.copy: + src: "{{ upload_dir.files[0].path }}/" + dest: "/var/www/{{ domain_name }}/" + remote_src: yes + owner: www-data + group: www-data + mode: '0755' + when: upload_dir.files + +- name: Copy all files to webroot (if no upload directory) + ansible.builtin.copy: + src: "/tmp/osticket_extract/" + dest: "/var/www/{{ domain_name }}/" + remote_src: yes + owner: www-data + group: www-data + mode: '0755' + when: not upload_dir.files + +- name: Check if sample config exists + ansible.builtin.stat: + path: "/var/www/{{ domain_name }}/include/ost-sampleconfig.php" + register: sample_config + +- name: Copy configuration file + ansible.builtin.copy: + src: "/var/www/{{ domain_name }}/include/ost-sampleconfig.php" + dest: "/var/www/{{ domain_name }}/include/ost-config.php" + owner: www-data + group: www-data + mode: '0666' + remote_src: yes + when: sample_config.stat.exists + +- name: Set correct permissions + ansible.builtin.file: + path: "/var/www/{{ domain_name }}/include" + state: directory + owner: www-data + group: www-data + mode: '0755' + recurse: yes + +- name: Create attachment directory + ansible.builtin.file: + path: "/var/www/{{ domain_name }}/attachments" + state: directory + owner: www-data + group: www-data + mode: '0777' + +- name: Clean up temp files + ansible.builtin.file: + path: "{{ item }}" + state: absent + loop: + - /tmp/osticket.zip + - /tmp/osticket_extract + +- name: Display completion message + ansible.builtin.debug: + msg: + - "✅ osTicket successfully installed!" + - "📍 Access web installer at: http://{{ domain_name }}/" + - "⚠️ Complete setup via browser" + - "🔒 After installation, run: chmod 0644 /var/www/{{ domain_name }}/include/ost-config.php" diff --git a/roles/php/tasks/main.yml b/roles/php/tasks/main.yml new file mode 100644 index 0000000..71c1c8c --- /dev/null +++ b/roles/php/tasks/main.yml @@ -0,0 +1,46 @@ +--- +- name: Install prerequisite packages for PHP repository + ansible.builtin.apt: + name: + - ca-certificates + - curl + - gpg + state: present + +- name: Add PHP repository key (Ondrej) + ansible.builtin.shell: | + curl -fsSL https://packages.sury.org/php/apt.gpg | gpg --dearmor -o /etc/apt/trusted.gpg.d/sury-php.gpg + args: + creates: /etc/apt/trusted.gpg.d/sury-php.gpg + +- name: Add PHP repository + ansible.builtin.apt_repository: + repo: "deb https://packages.sury.org/php/ {{ ansible_facts['lsb']['codename'] }} main" + state: present + update_cache: yes + +- name: Install PHP 8.4 and extensions + ansible.builtin.apt: + name: + - php8.4 + - libapache2-mod-php8.4 + - php8.4-mysql + - php8.4-cli + - php8.4-curl + - php8.4-gd + - php8.4-mbstring + - php8.4-xml + - php8.4-zip + - php8.4-intl + - php8.4-bcmath + - php8.4-imap + state: present + notify: Restart Apache + +- name: Set index.php priority + ansible.builtin.lineinfile: + path: /etc/apache2/mods-enabled/dir.conf + regexp: '^(\s*DirectoryIndex)' + line: '\1 index.php index.html index.cgi index.pl index.xhtml index.htm' + backrefs: yes + notify: Restart Apache diff --git a/roles/setup/tasks/main.yml b/roles/setup/tasks/main.yml new file mode 100644 index 0000000..f814e24 --- /dev/null +++ b/roles/setup/tasks/main.yml @@ -0,0 +1,9 @@ +--- +- name: Настройка sudo для пользователя без пароля + ansible.builtin.lineinfile: + path: /etc/sudoers + state: present + regexp: '^{{ ansible_user }} ALL=' + line: '{{ ansible_user }} ALL=(ALL) NOPASSWD:ALL' + validate: 'visudo -cf %s' + when: ansible_user != 'root' diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..8589767 --- /dev/null +++ b/run.sh @@ -0,0 +1,9 @@ +#!/bin/bash +# Переходим в папку с плейбуком +cd /home/ogrechko/osticket-ansible + +# Становимся root и запускаем ansible +sudo -i << 'SUDO' +cd /home/ogrechko/osticket-ansible +ansible-playbook install.yml +SUDO