diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..6313b56 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..938bb52 --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,69 @@ +name: CI + +# Na main não roda por push: o release.yml é quem o chama, e uma vez só por +# commit. Sem gatilho de pull_request: o push da branch já valida o mesmo commit, +# e os dois juntos rodariam tudo duas vezes (o repositório não recebe PR de fork). +on: + push: + branches-ignore: [main] + paths-ignore: ['**.md', 'LICENSE', '.claude/**', '.wolf/**', 'scripts/release-notes.sh'] + schedule: + - cron: '0 9 * * 1' # toda segunda: reavalia com a base de vulnerabilidades nova + workflow_call: + +permissions: + contents: read + +concurrency: + group: ci-${{ gitea.ref }} + cancel-in-progress: true + +jobs: + verificar: + name: Código e scripts + runs-on: ubuntu-24.04-arm + timeout-minutes: 10 + steps: + - uses: actions/checkout@v4 + - name: Sintaxe e shellcheck + run: | + python3 -m py_compile copiado.py + command -v shellcheck >/dev/null || { apt-get update -qq && apt-get install -y -qq shellcheck; } + shellcheck instalar.sh desinstalar.sh empacotamento/gerar-deb.sh scripts/*.sh + + # Trivy roda pela imagem oficial: o trivy-action clona o repositório do Trivy + # do mesmo servidor do workflow, que aqui é o Gitea. O workspace do job não é + # visível ao daemon, então os arquivos entram no container com `docker cp`. + seguranca: + name: Trivy (dependências, segredos e configuração) + runs-on: ubuntu-24.04-arm + steps: + - uses: actions/checkout@v4 + - run: | + cid=$(docker create --network host -v trivy-cache:/root/.cache/ -w /src aquasec/trivy:0.70.0 \ + fs --scanners vuln,secret,misconfig --severity HIGH,CRITICAL --exit-code 1 .) + docker cp . "$cid:/src" + docker start -a "$cid" + status=$(docker inspect -f '{{.State.ExitCode}}' "$cid") + docker rm "$cid" >/dev/null + exit "$status" + + pacote: + name: Pacote .deb + needs: [verificar, seguranca] + runs-on: ubuntu-24.04-arm + steps: + - uses: actions/checkout@v4 + - name: Gera o .deb + run: | + empacotamento/gerar-deb.sh "0.0.0~ci" + (cd dist && sha256sum copiado_all.deb > SHA256SUMS) + # Confere o conteúdo sem instalar: instalar puxaria python3-gi e o GTK só + # para provar que os arquivos estão no lugar. + - name: Confere o conteúdo do pacote + run: | + dpkg-deb -I dist/copiado_all.deb + mkdir conteudo && dpkg-deb -x dist/copiado_all.deb conteudo + test -x conteudo/usr/bin/copiado + test -f conteudo/etc/xdg/autostart/copiado.desktop + python3 -m py_compile conteudo/usr/bin/copiado diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml new file mode 100644 index 0000000..518a25b --- /dev/null +++ b/.gitea/workflows/release.yml @@ -0,0 +1,64 @@ +name: Release + +# Na main valida o commit (chamando o ci.yml). Numa tag v*, gera o .deb com a +# versão da tag e publica a release no Gitea com o pacote, o SHA256SUMS e as +# notas da seção do CHANGELOG.md. Se o commit da tag já passou na main, a suíte +# não roda de novo. +on: + push: + branches: [main] + paths-ignore: ['**.md', 'LICENSE', '.claude/**', '.wolf/**'] + tags: ['v*'] + workflow_dispatch: + +concurrency: + group: release + cancel-in-progress: false + +jobs: + plano: + name: Plano (este commit já foi validado?) + runs-on: ubuntu-24.04-arm + outputs: + reuse: ${{ steps.plano.outputs.reuse }} + steps: + - uses: actions/checkout@v4 + - id: plano + run: scripts/plano-ci.sh "${{ gitea.server_url }}" "${{ gitea.repository }}" "${{ gitea.sha }}" "${{ gitea.token }}" >> "$GITHUB_OUTPUT" + + ci: + needs: plano + if: ${{ needs.plano.outputs.reuse != 'true' }} + uses: ./.gitea/workflows/ci.yml + + release: + name: Publica a release + needs: [plano, ci] + # O ci é pulado, e não bem-sucedido, quando o commit já foi validado. + if: ${{ !cancelled() && needs.plano.result == 'success' && (needs.ci.result == 'success' || needs.plano.outputs.reuse == 'true') && startsWith(gitea.ref, 'refs/tags/v') }} + runs-on: ubuntu-24.04-arm + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Pacote + run: | + empacotamento/gerar-deb.sh "${GITEA_REF_NAME#v}" + (cd dist && sha256sum copiado_all.deb > SHA256SUMS) + env: + GITEA_REF_NAME: ${{ gitea.ref_name }} + - name: Release + env: + TAG: ${{ gitea.ref_name }} + TOKEN: ${{ gitea.token }} + API: ${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }} + run: | + scripts/release-notes.sh "$TAG" > notes.md + jq -n --arg tag "$TAG" --arg name "Copiado ${TAG#v}" --rawfile body notes.md \ + '{tag_name:$tag, name:$name, body:$body, prerelease:false}' > release.json + id=$(curl -fsS -X POST "$API/releases" -H "Authorization: token $TOKEN" \ + -H 'Content-Type: application/json' -d @release.json | jq -r .id) + for f in dist/copiado_all.deb dist/SHA256SUMS; do + curl -fsS -X POST "$API/releases/$id/assets?name=$(basename "$f")" \ + -H "Authorization: token $TOKEN" -F "attachment=@$f" >/dev/null + done diff --git a/.github/workflows/pacote.yml b/.github/workflows/pacote.yml deleted file mode 100644 index dfc2a1a..0000000 --- a/.github/workflows/pacote.yml +++ /dev/null @@ -1,126 +0,0 @@ -name: Pacote - -on: - push: - branches: [main] - tags: ["v*"] - pull_request: - schedule: - - cron: "0 9 * * 1" # toda segunda: reavalia com a base de vulnerabilidades nova - -# por padrão nenhum job pode escrever nada; cada um pede só o que precisa -permissions: - contents: read - -jobs: - verificar: - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - persist-credentials: false - - name: Checar código - run: | - python3 -m py_compile copiado.py - shellcheck instalar.sh desinstalar.sh empacotamento/gerar-deb.sh - - trivy: - runs-on: ubuntu-24.04 - permissions: - contents: read - security-events: write # envia o relatório para a aba Security do GitHub - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - persist-credentials: false - - - name: Trivy (relatório para a aba Security) - uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 - with: - scan-type: fs - scan-ref: . - scanners: vuln,secret,misconfig - format: sarif - output: trivy.sarif - exit-code: "0" - - - uses: github/codeql-action/upload-sarif@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2 - if: always() - with: - sarif_file: trivy.sarif - category: trivy - - - name: Trivy (barra a esteira em HIGH/CRITICAL) - uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 - with: - scan-type: fs - scan-ref: . - scanners: vuln,secret,misconfig - severity: HIGH,CRITICAL - format: table - exit-code: "1" - skip-setup-trivy: true - - pacote: - needs: [verificar, trivy] - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - persist-credentials: false - - - name: Definir versão - run: | - if [[ "$GITHUB_REF" == refs/tags/v* ]]; then - echo "VERSAO=${GITHUB_REF_NAME#v}" >> "$GITHUB_ENV" - else - echo "VERSAO=0.0.0~dev${GITHUB_RUN_NUMBER}" >> "$GITHUB_ENV" - fi - - - name: Gerar .deb - run: | - empacotamento/gerar-deb.sh "$VERSAO" - cd dist && sha256sum copiado_all.deb > SHA256SUMS - - - name: Testar instalação do pacote - run: | - sudo apt-get update -qq - sudo apt-get install -y ./dist/copiado_all.deb - test -x /usr/bin/copiado - test -f /etc/xdg/autostart/copiado.desktop - mkdir -p conteudo && dpkg-deb -x dist/copiado_all.deb conteudo - - - name: Trivy no conteúdo do pacote - uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 - with: - scan-type: fs - scan-ref: conteudo - scanners: vuln,secret - severity: HIGH,CRITICAL - exit-code: "1" - - - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: copiado - path: | - dist/copiado_all.deb - dist/SHA256SUMS - - release: - if: startsWith(github.ref, 'refs/tags/v') - needs: pacote - runs-on: ubuntu-24.04 - permissions: - contents: write # só este job pode publicar - steps: - - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: copiado - path: dist - - name: Publicar release - env: - GH_TOKEN: ${{ github.token }} - TAG: ${{ github.ref_name }} - run: | - gh release create "$TAG" dist/copiado_all.deb dist/SHA256SUMS \ - --repo "$GITHUB_REPOSITORY" --title "Copiado ${TAG#v}" --generate-notes diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..ecd634e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,64 @@ +# Registro de mudanças + +Tudo o que muda de relevante no Copiado está aqui. O formato segue o +[Keep a Changelog](https://keepachangelog.com/pt-BR/1.1.0/) e o projeto segue o +[Versionamento Semântico](https://semver.org/lang/pt-BR/). + +## [Não lançado] + +## [1.3.1] - 2026-11-01 + +### Alterado + +- O código e as releases passam a viver no Gitea (`git.lucasrguerra.dev.br`). O + repositório é privado: o `instalar.sh` agora exige `GITEA_TOKEN` e consulta a API do + Gitea para achar a release mais recente. +- A esteira de CI/CD foi reescrita para os runners do Gitea (`.gitea/workflows`): + branches validam (sintaxe, shellcheck, Trivy, pacote), a `main` e as tags chamam a + mesma suíte uma vez por commit, e a tag publica a release no Gitea com o `.deb`, o + `SHA256SUMS` e estas notas. O Trivy roda pela imagem oficial. + +## [1.3.0] - 2026-10-03 + +### Alterado + +- Senhas de gerenciadores de senha voltam a entrar no histórico. + +## [1.2.0] - 2026-10-03 + +### Adicionado + +- Comandos `copiado consertar` (recria e recarrega o atalho Win+V e inicia o app se + estiver parado) e `copiado ajuda`. + +### Alterado + +- `instalar.sh` e `desinstalar.sh` passam a usar os comandos do próprio app. + +## [1.1.0] - 2026-10-03 + +### Adicionado + +- Comando `copiado esquecer`, que apaga todo o histórico. +- Trivy (repositório e pacote) na esteira, ações fixadas por hash e permissões mínimas. +- A release publica o `SHA256SUMS` e o instalador confere o checksum antes de instalar. + +### Segurança + +- Só os itens fixados vão para o disco, com permissão 600/700. +- Conteúdo marcado por gerenciadores de senha é ignorado. +- Limites de tamanho e validação do histórico salvo. + +## [1.0.0] - 2026-10-03 + +### Adicionado + +- Histórico da área de transferência no Win+V, no visual do Windows 11, com texto e + imagens, fixação de itens e colagem por clique. + +[Não lançado]: https://git.lucasrguerra.dev.br/lucasrguerra/copiado/compare/v1.3.1...HEAD +[1.3.1]: https://git.lucasrguerra.dev.br/lucasrguerra/copiado/compare/v1.3.0...v1.3.1 +[1.3.0]: https://git.lucasrguerra.dev.br/lucasrguerra/copiado/compare/v1.2.0...v1.3.0 +[1.2.0]: https://git.lucasrguerra.dev.br/lucasrguerra/copiado/compare/v1.1.0...v1.2.0 +[1.1.0]: https://git.lucasrguerra.dev.br/lucasrguerra/copiado/compare/v1.0.0...v1.1.0 +[1.0.0]: https://git.lucasrguerra.dev.br/lucasrguerra/copiado/releases/tag/v1.0.0 diff --git a/README.md b/README.md index 2834765..a6d2854 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,16 @@ Clique em um item e ele é colado na hora. ## Instalar +O repositório é **privado**, então o instalador precisa de um token do Gitea +(Configurações → Aplicações, escopo `read:repository`): + ```bash -curl -fsSL https://raw.githubusercontent.com/lucasrguerra/copiado/main/instalar.sh | bash +export GITEA_TOKEN=... # seu token +curl -fsSL -H "Authorization: token $GITEA_TOKEN" \ + https://git.lucasrguerra.dev.br/lucasrguerra/copiado/raw/branch/main/instalar.sh | bash ``` -Baixa o pacote `.deb` mais recente das [Releases](https://github.com/lucasrguerra/copiado/releases), +Baixa o pacote `.deb` mais recente das [Releases](https://git.lucasrguerra.dev.br/lucasrguerra/copiado/releases), instala, cria o atalho Win+V e já deixa rodando. Pede sua senha (sudo). Para **atualizar**, rode o mesmo comando de novo. @@ -41,13 +46,25 @@ A área de transferência pode ter senhas, tokens e dados pessoais, então o Cop O instalador confere o checksum SHA-256 do pacote antes de instalar, e a esteira de CI/CD roda o [Trivy](https://trivy.dev) (vulnerabilidades, segredos e configurações) -em todo push, em toda release e toda segunda-feira. Os resultados ficam na aba -**Security** do repositório. +em todo push de branch, em toda release e toda segunda-feira. Um HIGH ou CRITICAL +barra a esteira. + +## Desenvolvimento + +- `copiado.py`: o aplicativo inteiro (GTK 3 + xdotool). +- `empacotamento/gerar-deb.sh `: gera `dist/copiado_all.deb`. +- `.gitea/workflows/`: `ci.yml` (sintaxe, shellcheck, Trivy, pacote) roda nas branches + e toda segunda; `release.yml` valida a `main` e, numa tag `vX.Y.Z`, publica a release + com o `.deb`, o `SHA256SUMS` e as notas do [CHANGELOG](CHANGELOG.md). Uma tag num + commit que já passou na `main` não repete a suíte. +- Para lançar: mova o que está em "Não lançado" do CHANGELOG para a versão nova, faça + o commit e envie a tag (`git tag vX.Y.Z && git push gitea vX.Y.Z`). ## Desinstalar ```bash -curl -fsSL https://raw.githubusercontent.com/lucasrguerra/copiado/main/desinstalar.sh | bash +curl -fsSL -H "Authorization: token $GITEA_TOKEN" \ + https://git.lucasrguerra.dev.br/lucasrguerra/copiado/raw/branch/main/desinstalar.sh | bash ``` ## Comandos diff --git a/empacotamento/gerar-deb.sh b/empacotamento/gerar-deb.sh index 21e373c..a725118 100755 --- a/empacotamento/gerar-deb.sh +++ b/empacotamento/gerar-deb.sh @@ -28,7 +28,7 @@ Priority: optional Architecture: all Depends: python3, python3-gi, gir1.2-gtk-3.0, xdotool Maintainer: Lucas Guerra -Homepage: https://github.com/lucasrguerra/copiado +Homepage: https://git.lucasrguerra.dev.br/lucasrguerra/copiado Description: Histórico da área de transferência no estilo Windows 11 Aperte Win+V para ver os últimos itens copiados e colar com um clique. CTRL diff --git a/instalar.sh b/instalar.sh index 57a5659..1544d7f 100755 --- a/instalar.sh +++ b/instalar.sh @@ -1,13 +1,31 @@ #!/usr/bin/env bash -# Instala (ou atualiza) o Copiado a partir do pacote publicado no GitHub +# Instala (ou atualiza) o Copiado a partir do pacote publicado no Gitea. +# O repositório é privado: exporte GITEA_TOKEN (token com read:repository). set -e -URL="https://github.com/lucasrguerra/copiado/releases/latest/download" +SERVIDOR="https://git.lucasrguerra.dev.br" +API="$SERVIDOR/api/v1/repos/lucasrguerra/copiado" +if [ -z "${GITEA_TOKEN:-}" ]; then + echo "ERRO: defina GITEA_TOKEN com um token do Gitea (read:repository)." >&2 + exit 1 +fi TMP=$(mktemp -d) trap 'rm -rf "$TMP"' EXIT -echo "==> Baixando a versão mais recente..." -curl -fsSL --proto '=https' --tlsv1.2 -o "$TMP/copiado_all.deb" "$URL/copiado_all.deb" -curl -fsSL --proto '=https' --tlsv1.2 -o "$TMP/SHA256SUMS" "$URL/SHA256SUMS" +# O token vai por um arquivo de configuração do curl, não pela linha de comando, +# para não aparecer na lista de processos. +printf 'header = "Authorization: token %s"\n' "$GITEA_TOKEN" > "$TMP/curlrc" + +baixar() { curl -fsSL --proto '=https' --tlsv1.2 -K "$TMP/curlrc" -o "$2" "$1"; } + +echo "==> Procurando a versão mais recente..." +baixar "$API/releases/latest" "$TMP/release.json" +url_do_arquivo() { + python3 -c 'import json,sys; print(next(a["browser_download_url"] for a in json.load(open(sys.argv[1]))["assets"] if a["name"] == sys.argv[2]))' "$TMP/release.json" "$1" +} + +echo "==> Baixando..." +baixar "$(url_do_arquivo copiado_all.deb)" "$TMP/copiado_all.deb" +baixar "$(url_do_arquivo SHA256SUMS)" "$TMP/SHA256SUMS" echo "==> Conferindo a integridade do pacote..." (cd "$TMP" && sha256sum --check --strict --quiet SHA256SUMS) || { diff --git a/scripts/plano-ci.sh b/scripts/plano-ci.sh new file mode 100755 index 0000000..1d2dabc --- /dev/null +++ b/scripts/plano-ci.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +# Uso: scripts/plano-ci.sh +# Imprime reuse=true quando este commit já passou por uma execução bem-sucedida +# (o caso de uma tag num commit da main que já foi validado): não há o que +# verificar de novo, o conteúdo é idêntico. +set -euo pipefail +server=$1; repo=$2; sha=$3; token=$4 +ok=$(curl -fsS -H "Authorization: token $token" \ + "$server/api/v1/repos/$repo/actions/runs?head_sha=$sha&status=success&limit=5" \ + | jq '[.workflow_runs[]? | select(.event == "push")] | length') +echo "commit ${sha:0:8}: $ok execução(ões) bem-sucedida(s)" >&2 +reuse=false; [ "$ok" -gt 0 ] && reuse=true +echo "reuse=$reuse" diff --git a/scripts/release-notes.sh b/scripts/release-notes.sh new file mode 100755 index 0000000..7664192 --- /dev/null +++ b/scripts/release-notes.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +# Imprime as notas de uma release: a imagem publicada (quando houver) e a seção +# do CHANGELOG.md daquela versão (ou o log de commits desde a tag anterior, se +# não houver seção). +# +# scripts/release-notes.sh v1.2.3 [git.exemplo.com/dono/projeto] +set -euo pipefail + +tag=${1:?uso: release-notes.sh [imagem]} +image=${2:-} +version=${tag#v} + +if [ -n "$image" ]; then + echo "## Imagem" + echo + echo '```' + echo "$image:$version" + echo '```' + echo +fi + +section="" +if [ -f CHANGELOG.md ]; then + section=$(awk -v head="## [$version]" ' + index($0, head) == 1 { found = 1; next } + found && /^## \[/ { exit } + found && /^\[[^]]+\]: / { next } + found { print } + ' CHANGELOG.md) +fi + +if [ -n "$(printf '%s' "$section" | tr -d '[:space:]')" ]; then + printf '%s\n' "$section" +else + previous=$(git describe --tags --abbrev=0 "$tag^" 2>/dev/null || true) + echo "## Mudanças${previous:+ desde $previous}" + echo + git log --no-merges --format='- %s (%h)' "${previous:+$previous..}$tag" +fi