From dd221cff888f0bcb1aea372221719764cf2a8bfc Mon Sep 17 00:00:00 2001 From: rafapolo Date: Wed, 25 Mar 2026 10:13:34 +0100 Subject: [PATCH] =?UTF-8?q?add=20export=20pipeline:=20BigQuery=20=E2=86=92?= =?UTF-8?q?=20GCS=20=E2=86=92=20Hetzner=20S3=20(roda.sh)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- all_tables.txt | 535 ++++++++++++++++++++++++++++++++++++++++++++++ failed_tables.txt | 6 + roda.sh | 527 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1068 insertions(+) create mode 100644 all_tables.txt create mode 100644 failed_tables.txt create mode 100755 roda.sh diff --git a/all_tables.txt b/all_tables.txt new file mode 100644 index 0000000..9cc050d --- /dev/null +++ b/all_tables.txt @@ -0,0 +1,535 @@ +br_anatel_banda_larga_fixa.densidade_brasil +br_anatel_banda_larga_fixa.densidade_municipio +br_anatel_banda_larga_fixa.densidade_uf +br_anatel_banda_larga_fixa.microdados +br_anatel_indice_brasileiro_conectividade.municipio +br_anp_precos_combustiveis.microdados +br_ans_beneficiario.informacao_consolidada +br_bcb_estban.agencia +br_bcb_estban.dicionario +br_bcb_estban.municipio +br_bcb_sicor.dicionario +br_bcb_sicor.empreendimento +br_bcb_sicor.liberacao +br_bcb_sicor.operacao +br_bcb_sicor.operacoes_desclassificadas +br_bcb_sicor.recurso_publico_complemento_operacao +br_bcb_sicor.recurso_publico_cooperado +br_bcb_sicor.recurso_publico_gleba +br_bcb_sicor.recurso_publico_mutuario +br_bcb_sicor.recurso_publico_propriedade +br_bcb_sicor.saldo +br_bcb_taxa_cambio.taxa_cambio +br_bcb_taxa_selic.taxa_selic +br_bd_diretorios_brasil.area_conhecimento +br_bd_diretorios_brasil.cbo_1994 +br_bd_diretorios_brasil.cbo_2002 +br_bd_diretorios_brasil.cep +br_bd_diretorios_brasil.cid_10 +br_bd_diretorios_brasil.cid_9 +br_bd_diretorios_brasil.cnae_1 +br_bd_diretorios_brasil.cnae_2 +br_bd_diretorios_brasil.curso_superior +br_bd_diretorios_brasil.distrito_1991 +br_bd_diretorios_brasil.distrito_2000 +br_bd_diretorios_brasil.distrito_2010 +br_bd_diretorios_brasil.empresa +br_bd_diretorios_brasil.escola +br_bd_diretorios_brasil.etnia_indigena +br_bd_diretorios_brasil.instituicao_ensino_superior +br_bd_diretorios_brasil.municipio +br_bd_diretorios_brasil.natureza_juridica +br_bd_diretorios_brasil.regiao +br_bd_diretorios_brasil.setor_censitario_2010 +br_bd_diretorios_brasil.setor_censitario_2022 +br_bd_diretorios_brasil.subatividade_ibge +br_bd_diretorios_brasil.uf +br_bd_diretorios_mundo.continente +br_bd_diretorios_mundo.nomenclatura_comum_mercosul +br_bd_diretorios_mundo.pais +br_bd_diretorios_mundo.sistema_harmonizado +br_bd_metadados.bigquery_tables +br_bd_metadados.prefect_flow_runs +br_camara_dados_abertos.deputado +br_camara_dados_abertos.deputado_ocupacao +br_camara_dados_abertos.deputado_profissao +br_camara_dados_abertos.despesa +br_camara_dados_abertos.evento +br_camara_dados_abertos.evento_orgao +br_camara_dados_abertos.evento_presenca_deputado +br_camara_dados_abertos.evento_requerimento +br_camara_dados_abertos.frente +br_camara_dados_abertos.frente_deputado +br_camara_dados_abertos.funcionario +br_camara_dados_abertos.legislatura +br_camara_dados_abertos.legislatura_mesa +br_camara_dados_abertos.licitacao +br_camara_dados_abertos.licitacao_contrato +br_camara_dados_abertos.licitacao_item +br_camara_dados_abertos.licitacao_pedido +br_camara_dados_abertos.licitacao_proposta +br_camara_dados_abertos.orgao +br_camara_dados_abertos.orgao_deputado +br_camara_dados_abertos.proposicao_autor +br_camara_dados_abertos.proposicao_microdados +br_camara_dados_abertos.proposicao_tema +br_camara_dados_abertos.votacao +br_camara_dados_abertos.votacao_objeto +br_camara_dados_abertos.votacao_orientacao_bancada +br_camara_dados_abertos.votacao_parlamentar +br_camara_dados_abertos.votacao_proposicao +br_ce_fortaleza_sefin_iptu.face_quadra +br_cgu_beneficios_cidadao.auxilio_brasil +br_cgu_beneficios_cidadao.auxilio_emergencial +br_cgu_beneficios_cidadao.bolsa_familia_pagamento +br_cgu_beneficios_cidadao.bpc +br_cgu_beneficios_cidadao.garantia_safra +br_cgu_beneficios_cidadao.novo_bolsa_familia +br_cgu_cartao_pagamento.dicionario +br_cgu_cartao_pagamento.microdados_compras_centralizadas +br_cgu_cartao_pagamento.microdados_defesa_civil +br_cgu_cartao_pagamento.microdados_governo_federal +br_cgu_dados_abertos.conjunto +br_cgu_dados_abertos.organizacao +br_cgu_dados_abertos.recurso +br_cgu_emendas_parlamentares.microdados +br_cgu_licitacao_contrato.contrato_apostilamento +br_cgu_licitacao_contrato.contrato_compra +br_cgu_licitacao_contrato.contrato_item +br_cgu_licitacao_contrato.contrato_termo_aditivo +br_cgu_licitacao_contrato.licitacao +br_cgu_licitacao_contrato.licitacao_empenho +br_cgu_licitacao_contrato.licitacao_item +br_cgu_licitacao_contrato.licitacao_participante +br_cgu_orcamento_publico.orcamento +br_cgu_receitas_publicas.receitas +br_cgu_servidores_executivo_federal.afastamentos +br_cgu_servidores_executivo_federal.cadastro_aposentados +br_cgu_servidores_executivo_federal.cadastro_pensionistas +br_cgu_servidores_executivo_federal.cadastro_reserva_reforma_militares +br_cgu_servidores_executivo_federal.cadastro_servidores +br_cgu_servidores_executivo_federal.observacoes +br_cgu_servidores_executivo_federal.remuneracao +br_cnj_improbidade_administrativa.condenacao +br_cnpq_bolsas.dicionario +br_cnpq_bolsas.microdados +br_cvm_administradores_carteira.pessoa_fisica +br_cvm_administradores_carteira.pessoa_juridica +br_cvm_administradores_carteira.responsavel +br_cvm_oferta_publica_distribuicao.dia +br_datahackers_state_data.microdados +br_fbsp_absp.uf +br_fbsp_absp.violencia_escola +br_fgv_igp.igp_10_mes +br_fgv_igp.igp_di_ano +br_fgv_igp.igp_di_mes +br_fgv_igp.igp_m_ano +br_fgv_igp.igp_m_mes +br_fgv_igp.igp_og_ano +br_fgv_igp.igp_og_mes +br_geobr_mapas.amazonia_legal +br_geobr_mapas.area_minima_comparavel_2010 +br_geobr_mapas.area_risco_desastre +br_geobr_mapas.arranjo_populacional +br_geobr_mapas.bioma +br_geobr_mapas.concentracao_urbana +br_geobr_mapas.escola +br_geobr_mapas.estabelecimentos_saude +br_geobr_mapas.limite_vizinhanca +br_geobr_mapas.mesorregiao +br_geobr_mapas.microrregiao +br_geobr_mapas.municipio +br_geobr_mapas.pais +br_geobr_mapas.pegada_urbana +br_geobr_mapas.regiao +br_geobr_mapas.regiao_imediata +br_geobr_mapas.regiao_intermediaria +br_geobr_mapas.regiao_metropolitana_2017 +br_geobr_mapas.saude +br_geobr_mapas.sede_municipal +br_geobr_mapas.semiarido +br_geobr_mapas.setor_censitario_2010 +br_geobr_mapas.terra_indigena +br_geobr_mapas.uf +br_geobr_mapas.unidade_conservacao +br_ibge_censo_2022.alfabetizacao_grupo_idade_sexo_raca +br_ibge_censo_2022.cadastro_enderecos +br_ibge_censo_2022.caracteristica_domicilio_grupo_idade_raca_destino_lixo +br_ibge_censo_2022.caracteristica_domicilio_grupo_idade_raca_esgotamento_sanitario +br_ibge_censo_2022.caracteristica_domicilio_grupo_idade_raca_ligacao_abastecimento_agua +br_ibge_censo_2022.caracteristica_domicilio_grupo_idade_raca_tipo_domicilio +br_ibge_censo_2022.dicionario +br_ibge_censo_2022.domicilio_recenseado +br_ibge_censo_2022.indice_envelhecimento_raca +br_ibge_censo_2022.municipio +br_ibge_censo_2022.populacao_grupo_idade_sexo_raca +br_ibge_censo_2022.populacao_grupo_idade_uf +br_ibge_censo_2022.populacao_idade_sexo +br_ibge_censo_2022.setor_censitario +br_ibge_censo_2022.terra_indigena +br_ibge_censo_2022.territorio_quilombola +br_ibge_censo_demografico.dicionario +br_ibge_censo_demografico.microdados_domicilio_1970 +br_ibge_censo_demografico.microdados_domicilio_1980 +br_ibge_censo_demografico.microdados_domicilio_1991 +br_ibge_censo_demografico.microdados_domicilio_2000 +br_ibge_censo_demografico.microdados_domicilio_2010 +br_ibge_censo_demografico.microdados_pessoa_1970 +br_ibge_censo_demografico.microdados_pessoa_1980 +br_ibge_censo_demografico.microdados_pessoa_1991 +br_ibge_censo_demografico.microdados_pessoa_2000 +br_ibge_censo_demografico.microdados_pessoa_2010 +br_ibge_censo_demografico.setor_censitario_alfabetizacao_homens_mulheres_2010 +br_ibge_censo_demografico.setor_censitario_alfabetizacao_total_2010 +br_ibge_censo_demografico.setor_censitario_basico_2010 +br_ibge_censo_demografico.setor_censitario_domicilio_caracteristicas_gerais_2010 +br_ibge_censo_demografico.setor_censitario_domicilio_moradores_2010 +br_ibge_censo_demografico.setor_censitario_domicilio_renda_2010 +br_ibge_censo_demografico.setor_censitario_entorno_2010 +br_ibge_censo_demografico.setor_censitario_idade_homens_2010 +br_ibge_censo_demografico.setor_censitario_idade_mulheres_2010 +br_ibge_censo_demografico.setor_censitario_idade_total_2010 +br_ibge_censo_demografico.setor_censitario_pessoa_renda_2010 +br_ibge_censo_demografico.setor_censitario_raca_alfabetizacao_idade_genero_2010 +br_ibge_censo_demografico.setor_censitario_raca_idade_0_4_genero_2010 +br_ibge_censo_demografico.setor_censitario_raca_idade_genero_2010 +br_ibge_censo_demografico.setor_censitario_registro_civil_2010 +br_ibge_censo_demografico.setor_censitario_relacao_parentesco_conjuges_2010 +br_ibge_censo_demografico.setor_censitario_relacao_parentesco_filhos_2010 +br_ibge_censo_demografico.setor_censitario_relacao_parentesco_filhos_enteados_2010 +br_ibge_censo_demografico.setor_censitario_relacao_parentesco_outros_2010 +br_ibge_censo_demografico.setor_censitario_responsavel_domicilios_homens_total_2010 +br_ibge_censo_demografico.setor_censitario_responsavel_domicilios_mulheres_2010 +br_ibge_censo_demografico.setor_censitario_responsavel_renda_2010 +br_ibge_estadic.dicionario +br_ibge_inpc.mes_brasil +br_ibge_inpc.mes_categoria_brasil +br_ibge_inpc.mes_categoria_municipio +br_ibge_inpc.mes_categoria_rm +br_ibge_ipca.mes_brasil +br_ibge_ipca.mes_categoria_brasil +br_ibge_ipca.mes_categoria_municipio +br_ibge_ipca.mes_categoria_rm +br_ibge_ipca15.mes_brasil +br_ibge_ipca15.mes_categoria_brasil +br_ibge_ipca15.mes_categoria_municipio +br_ibge_ipca15.mes_categoria_rm +br_ibge_pam.lavoura_permanente +br_ibge_pam.lavoura_temporaria +br_ibge_pevs.producao_extracao_vegetal +br_ibge_pevs.producao_silvicultura +br_ibge_pib.gini +br_ibge_pib.municipio +br_ibge_pnad.dicionario +br_ibge_pnad.microdados_compatibilizados_domicilio +br_ibge_pnad.microdados_compatibilizados_pessoa +br_ibge_pnad_covid.dicionario +br_ibge_pnadc.dicionario +br_ibge_pnadc.educacao +br_ibge_pnadc.microdados +br_ibge_pnadc.rendimentos_outras_fontes +br_ibge_pof.dicionario +br_ibge_populacao.brasil +br_ibge_populacao.municipio +br_ibge_populacao.uf +br_ibge_ppm.efetivo_rebanhos +br_ibge_ppm.producao_aquicultura +br_ibge_ppm.producao_origem_animal +br_ibge_ppm.producao_pecuaria +br_inep_ana.dicionario +br_inep_avaliacao_alfabetizacao.alunos +br_inep_avaliacao_alfabetizacao.dicionario +br_inep_avaliacao_alfabetizacao.meta_alfabetizacao_brasil +br_inep_avaliacao_alfabetizacao.meta_alfabetizacao_municipio +br_inep_avaliacao_alfabetizacao.meta_alfabetizacao_uf +br_inep_avaliacao_alfabetizacao.municipio +br_inep_avaliacao_alfabetizacao.uf +br_inep_censo_educacao_superior.curso +br_inep_censo_educacao_superior.dicionario +br_inep_censo_educacao_superior.ies +br_inep_censo_escolar.dicionario +br_inep_censo_escolar.escola +br_inep_censo_escolar.turma +br_inep_educacao_especial.brasil_distorcao_idade_serie +br_inep_educacao_especial.brasil_taxa_rendimento +br_inep_educacao_especial.distorcao_idade_serie +br_inep_educacao_especial.docente_aee +br_inep_educacao_especial.docente_formacao +br_inep_educacao_especial.etapa_ensino +br_inep_educacao_especial.faixa_etaria +br_inep_educacao_especial.localizacao +br_inep_educacao_especial.matricula_aee +br_inep_educacao_especial.sexo_raca_cor +br_inep_educacao_especial.taxa_rendimento +br_inep_educacao_especial.tempo_ensino +br_inep_educacao_especial.tipo_deficiencia +br_inep_educacao_especial.uf_distorcao_idade_serie +br_inep_educacao_especial.uf_taxa_rendimento +br_inep_enem.dicionario +br_inep_enem.microdados +br_inep_enem.questionario_socioeconomico_1998 +br_inep_enem.questionario_socioeconomico_1999 +br_inep_enem.questionario_socioeconomico_2000 +br_inep_enem.questionario_socioeconomico_2001 +br_inep_enem.questionario_socioeconomico_2002 +br_inep_enem.questionario_socioeconomico_2003 +br_inep_enem.questionario_socioeconomico_2004 +br_inep_enem.questionario_socioeconomico_2005 +br_inep_enem.questionario_socioeconomico_2006 +br_inep_enem.questionario_socioeconomico_2007 +br_inep_enem.questionario_socioeconomico_2008 +br_inep_enem.questionario_socioeconomico_2009 +br_inep_enem.questionario_socioeconomico_2010 +br_inep_enem.questionario_socioeconomico_2011 +br_inep_enem.questionario_socioeconomico_2012 +br_inep_enem.questionario_socioeconomico_2013 +br_inep_enem.questionario_socioeconomico_2014 +br_inep_enem.questionario_socioeconomico_2015 +br_inep_enem.questionario_socioeconomico_2016 +br_inep_enem.questionario_socioeconomico_2017 +br_inep_enem.questionario_socioeconomico_2018 +br_inep_enem.questionario_socioeconomico_2019 +br_inep_enem.questionario_socioeconomico_2020 +br_inep_enem.questionario_socioeconomico_2021 +br_inep_enem.questionario_socioeconomico_2022 +br_inep_enem.questionario_socioeconomico_2023 +br_inep_formacao_docente.dicionario +br_inep_ideb.brasil +br_inep_ideb.escola +br_inep_ideb.municipio +br_inep_ideb.regiao +br_inep_ideb.uf +br_inep_indicador_nivel_socioeconomico.dicionario +br_inep_indicador_nivel_socioeconomico.escola +br_inep_indicadores_educacionais.brasil +br_inep_indicadores_educacionais.brasil_remuneracao_docentes +br_inep_indicadores_educacionais.brasil_taxa_transicao +br_inep_indicadores_educacionais.escola +br_inep_indicadores_educacionais.municipio +br_inep_indicadores_educacionais.municipio_taxa_transicao +br_inep_indicadores_educacionais.regiao +br_inep_indicadores_educacionais.regiao_taxa_transicao +br_inep_indicadores_educacionais.uf +br_inep_indicadores_educacionais.uf_remuneracao_docentes +br_inep_indicadores_educacionais.uf_taxa_transicao +br_inep_saeb.aluno_ef_2ano +br_inep_saeb.aluno_ef_5ano +br_inep_saeb.aluno_ef_9ano +br_inep_saeb.aluno_em_34ano +br_inep_saeb.brasil +br_inep_saeb.brasil_taxa_alfabetizacao +br_inep_saeb.dicionario +br_inep_saeb.municipio +br_inep_saeb.proficiencia +br_inep_saeb.uf +br_inep_saeb.uf_taxa_alfabetizacao +br_inep_sinopse_estatistica_educacao_basica.dicionario +br_inep_sinopse_estatistica_educacao_basica.docente_deficiencia +br_inep_sinopse_estatistica_educacao_basica.docente_escolaridade +br_inep_sinopse_estatistica_educacao_basica.docente_etapa_ensino +br_inep_sinopse_estatistica_educacao_basica.docente_faixa_etaria_sexo +br_inep_sinopse_estatistica_educacao_basica.docente_localizacao +br_inep_sinopse_estatistica_educacao_basica.docente_regime_contrato +br_inep_sinopse_estatistica_educacao_basica.educacao_especial_etapa_ensino +br_inep_sinopse_estatistica_educacao_basica.educacao_especial_faixa_etaria +br_inep_sinopse_estatistica_educacao_basica.educacao_especial_localizacao +br_inep_sinopse_estatistica_educacao_basica.educacao_especial_sexo_raca_cor +br_inep_sinopse_estatistica_educacao_basica.educacao_especial_tempo_ensino +br_inep_sinopse_estatistica_educacao_basica.educacao_especial_tipo_deficiencia +br_inep_sinopse_estatistica_educacao_basica.etapa_ensino_serie +br_inep_sinopse_estatistica_educacao_basica.faixa_etaria +br_inep_sinopse_estatistica_educacao_basica.localizacao +br_inep_sinopse_estatistica_educacao_basica.sexo_raca_cor +br_inep_sinopse_estatistica_educacao_basica.tempo_ensino +br_inmet_bdmep.microdados +br_inpe_prodes.municipio_bioma +br_inpe_queimadas.microdados +br_inpe_sisam.microdados +br_ipea_avs.municipio +br_mdr_snis.municipio_agua_esgoto +br_mdr_snis.prestador_agua_esgoto +br_me_caged.dicionario +br_me_caged.microdados_movimentacao +br_me_caged.microdados_movimentacao_excluida +br_me_caged.microdados_movimentacao_fora_prazo +br_me_cno.dicionario +br_me_cnpj.dicionario +br_me_cnpj.empresas +br_me_cnpj.estabelecimentos +br_me_cnpj.simples +br_me_cnpj.socios +br_me_comex_stat.dicionario +br_me_comex_stat.municipio_exportacao +br_me_comex_stat.municipio_importacao +br_me_comex_stat.ncm_exportacao +br_me_comex_stat.ncm_importacao +br_me_rais.dicionario +br_me_rais.microdados_estabelecimentos +br_me_rais.microdados_vinculos +br_me_sic.dicionario +br_me_sic.transferencia +br_me_siconfi.municipio_balanco_patrimonial +br_me_siconfi.municipio_despesas_funcao +br_me_siconfi.municipio_despesas_orcamentarias +br_me_siconfi.municipio_receitas_orcamentarias +br_me_siconfi.uf_despesas_funcao +br_me_siconfi.uf_despesas_orcamentarias +br_me_siconfi.uf_receitas_orcamentarias +br_mec_prouni.dicionario +br_mec_sisu.microdados +br_mg_belohorizonte_smfa_iptu.dicionario +br_mg_belohorizonte_smfa_iptu.iptu +br_mme_consumo_energia_eletrica.uf +br_mp_pep.cargos_funcoes +br_ms_cnes.dados_complementares +br_ms_cnes.dicionario +br_ms_cnes.equipamento +br_ms_cnes.equipe +br_ms_cnes.estabelecimento +br_ms_cnes.estabelecimento_ensino +br_ms_cnes.estabelecimento_filantropico +br_ms_cnes.gestao_metas +br_ms_cnes.habilitacao +br_ms_cnes.incentivos +br_ms_cnes.leito +br_ms_cnes.profissional +br_ms_cnes.regra_contratual +br_ms_cnes.servico_especializado +br_ms_pns.dicionario +br_ms_pns.microdados_2013 +br_ms_pns.microdados_2019 +br_ms_populacao.municipio +br_ms_sia.dicionario +br_ms_sia.producao_ambulatorial +br_ms_sia.psicossocial +br_ms_sih.aihs_reduzidas +br_ms_sih.dicionario +br_ms_sih.servicos_profissionais +br_ms_sim.dicionario +br_ms_sim.microdados +br_ms_sinan.dicionario +br_ms_sinan.microdados_dengue +br_ms_sinan.microdados_influenza_srag +br_ms_sinasc.dicionario +br_ms_sinasc.microdados +br_ms_sisvan.dicionario +br_ms_sisvan.microdados +br_ms_vacinacao_covid19.dicionario +br_poder360_pesquisas.microdados +br_rf_arrecadacao.cnae +br_rf_arrecadacao.ir_ipi +br_rf_arrecadacao.itr +br_rf_arrecadacao.natureza_juridica +br_rf_arrecadacao.uf +br_rf_cafir.dicionario +br_rf_cafir.imoveis_rurais +br_rf_cno.areas +br_rf_cno.cnaes +br_rf_cno.dicionario +br_rf_cno.microdados +br_rf_cno.vinculos +br_rj_isp_estatisticas_seguranca.armas_apreendidas_mensal +br_rj_isp_estatisticas_seguranca.armas_fogo_apreendidas_mensal +br_rj_isp_estatisticas_seguranca.evolucao_mensal_cisp +br_rj_isp_estatisticas_seguranca.evolucao_mensal_municipio +br_rj_isp_estatisticas_seguranca.evolucao_mensal_uf +br_rj_isp_estatisticas_seguranca.evolucao_mensal_upp +br_rj_isp_estatisticas_seguranca.evolucao_policial_morto_servico_mensal +br_rj_isp_estatisticas_seguranca.feminicidio_mensal_cisp +br_rj_isp_estatisticas_seguranca.relacao_cisp_aisp_risp +br_rj_isp_estatisticas_seguranca.taxa_evolucao_anual_municipio +br_rj_isp_estatisticas_seguranca.taxa_evolucao_anual_uf +br_rj_isp_estatisticas_seguranca.taxa_evolucao_mensal_municipio +br_rj_isp_estatisticas_seguranca.taxa_evolucao_mensal_uf +br_rj_isp_estatisticas_seguranca.taxa_letalidade +br_seeg_emissoes.dicionario +br_seeg_emissoes.municipio +br_seeg_emissoes.uf +br_sfb_sicar.area_imovel +br_sfb_sicar.dicionario +br_simet_educacao_conectada.escola +br_sp_saopaulo_geosampa_iptu.iptu +br_stf_corte_aberta.decisoes +br_stf_corte_aberta.dicionario +br_trase_supply_chain.beef +br_trase_supply_chain.beef_slaughterhouses +br_trase_supply_chain.soy_beans +br_trase_supply_chain.soy_beans_crushing_facilities +br_trase_supply_chain.soy_beans_refining_facilities +br_trase_supply_chain.soy_beans_storage_facilities +br_tse_eleicoes.bens_candidato +br_tse_eleicoes.candidatos +br_tse_eleicoes.despesas_candidato +br_tse_eleicoes.detalhes_votacao_municipio +br_tse_eleicoes.detalhes_votacao_municipio_zona +br_tse_eleicoes.detalhes_votacao_secao +br_tse_eleicoes.dicionario +br_tse_eleicoes.partidos +br_tse_eleicoes.perfil_eleitorado_local_votacao +br_tse_eleicoes.perfil_eleitorado_municipio_zona +br_tse_eleicoes.perfil_eleitorado_secao +br_tse_eleicoes.receitas_candidato +br_tse_eleicoes.receitas_comite +br_tse_eleicoes.receitas_orgao_partidario +br_tse_eleicoes.resultados_candidato +br_tse_eleicoes.resultados_candidato_municipio +br_tse_eleicoes.resultados_candidato_municipio_zona +br_tse_eleicoes.resultados_candidato_secao +br_tse_eleicoes.resultados_partido_municipio +br_tse_eleicoes.resultados_partido_municipio_zona +br_tse_eleicoes.resultados_partido_secao +br_tse_eleicoes.vagas +br_tse_filiacao_partidaria.microdados +br_tse_filiacao_partidaria.microdados_antigos +dataset_new_arch.tabela_new_arch +logs.cloudaudit_googleapis_com_activity +logs.cloudaudit_googleapis_com_data_access +mundo_transfermarkt_competicoes.brasileirao_serie_a +mundo_transfermarkt_competicoes.copa_brasil +mundo_transfermarkt_competicoes_internacionais.champions_league +test_dataset.test_table +us_harvard_ned.parliamentary_elections +us_harvard_ned.presidential_elections +world_ampas_oscar.winner_demographics +world_iea_pirls.dictionary +world_iea_pirls.home_context +world_iea_pirls.school_context +world_iea_pirls.student_achievement +world_iea_pirls.student_context +world_iea_pirls.student_teacher_link +world_iea_pirls.teacher_context +world_iea_pirls.within_country_scoring_reliability +world_iea_timss.dictionary +world_iea_timss.home_context_grade_4 +world_iea_timss.school_context_grade_4 +world_iea_timss.school_context_grade_8 +world_iea_timss.student_achievement_grade_4 +world_iea_timss.student_achievement_grade_8 +world_iea_timss.student_context_grade_4 +world_iea_timss.student_context_grade_8 +world_iea_timss.teacher_context_grade_4 +world_iea_timss.teacher_mathematics_grade_8 +world_iea_timss.teacher_science_grade_8 +world_imdb_movies.top_movies_per_year +world_oecd_pisa.student +world_oecd_public_finance.country +world_olympedia_olympics.athlete_bio +world_olympedia_olympics.athlete_event_result +world_olympedia_olympics.country +world_olympedia_olympics.game +world_olympedia_olympics.game_medal_tally +world_olympedia_olympics.result +world_sofascore_competicoes_futebol.brasileirao_serie_a +world_sofascore_competicoes_futebol.uefa_champions_league +world_wb_mides.dicionario +world_wb_mides.empenho +world_wb_mides.licitacao +world_wb_mides.licitacao_item +world_wb_mides.licitacao_participante +world_wb_mides.liquidacao +world_wb_mides.orgao_unidade_gestora +world_wb_mides.pagamento +world_wb_mides.relacionamentos +world_wwf_hydrosheds.basins_atlas +world_wwf_hydrosheds.lakes_atlas +world_wwf_hydrosheds.rivers_atlas diff --git a/failed_tables.txt b/failed_tables.txt new file mode 100644 index 0000000..94b5cc9 --- /dev/null +++ b/failed_tables.txt @@ -0,0 +1,6 @@ +[ACCESS_DENIED] br_bcb_taxa_cambio.taxa_cambio +[ACCESS_DENIED] br_bcb_taxa_selic.taxa_selic +[ACCESS_DENIED] br_bcb_taxa_cambio.taxa_cambio +[ACCESS_DENIED] br_bcb_taxa_selic.taxa_selic +[ACCESS_DENIED] br_bcb_taxa_cambio.taxa_cambio +[ACCESS_DENIED] br_bcb_taxa_selic.taxa_selic diff --git a/roda.sh b/roda.sh new file mode 100755 index 0000000..89f9df4 --- /dev/null +++ b/roda.sh @@ -0,0 +1,527 @@ +#!/usr/bin/env bash +# ============================================================================= +# export_basedosdados.sh +# Exports all basedosdados BigQuery tables → GCS (Parquet+zstd) → Hetzner Object Storage +# +# Prerequisites (run once before this script): +# gcloud auth login +# gcloud auth application-default login +# gcloud config set project YOUR_PROJECT_ID +# cp .env.example .env # then fill in your values +# +# Usage: +# chmod +x export_basedosdados.sh +# ./export_basedosdados.sh # full run (locally) +# ./export_basedosdados.sh --dry-run # list tables + estimated sizes, no export +# ./export_basedosdados.sh --gcloud-run # create GCP VM → run there → delete VM +# ============================================================================= + +set -euo pipefail + +# Add util-linux to PATH on macOS (provides flock) +[[ -d "/opt/homebrew/opt/util-linux/bin" ]] && export PATH="/opt/homebrew/opt/util-linux/bin:$PATH" + +# Load .env if present +if [[ -f "$(dirname "$0")/.env" ]]; then + set -a + # shellcheck source=.env + source "$(dirname "$0")/.env" + set +a +fi + +DRY_RUN=false +GCLOUD_RUN=false +if [[ "${1:-}" == "--dry-run" ]]; then + DRY_RUN=true +elif [[ "${1:-}" == "--gcloud-run" ]]; then + GCLOUD_RUN=true +fi + +# ----------------------------------------------------------------------------- +# LOGGING +# ----------------------------------------------------------------------------- +LOG_FILE="export_$(date +%Y%m%d_%H%M%S).log" +FAILED_FILE="failed_tables.txt" +DONE_FILE="done_tables.txt" +DONE_TRANSFERS_FILE="done_transfers.txt" + +log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"; } +log_err() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $*" | tee -a "$LOG_FILE" >&2; } + +# ----------------------------------------------------------------------------- +# STEP 0 — Verify dependencies +# ----------------------------------------------------------------------------- +log "Checking dependencies..." +if $GCLOUD_RUN; then + for cmd in gcloud; do + if ! command -v "$cmd" &>/dev/null; then + log_err "'$cmd' not found. Install google-cloud-sdk." + exit 1 + fi + done +else + for cmd in bq gcloud gsutil parallel rclone flock; do + if ! command -v "$cmd" &>/dev/null; then + log_err "'$cmd' not found. Install google-cloud-sdk, GNU parallel, and rclone." + exit 1 + fi + done +fi + +# Validate S3 credentials +if [[ -z "${AWS_ACCESS_KEY_ID:-}" || -z "${AWS_SECRET_ACCESS_KEY:-}" ]]; then + log_err "Credenciais S3 não encontradas. Preencha o .env com AWS_ACCESS_KEY_ID e AWS_SECRET_ACCESS_KEY." + exit 1 +fi + +# Configure rclone remotes via env vars — no rclone.conf or inline credentials needed. +# GCS remote (bd:) uses Application Default Credentials from gcloud auth application-default login. +# Hetzner S3 remote (hz:) uses the credentials from .env, kept out of the process command line. +export RCLONE_CONFIG_BD_TYPE="google cloud storage" +export RCLONE_CONFIG_BD_BUCKET_POLICY_ONLY="true" +export RCLONE_CONFIG_HZ_TYPE="s3" +export RCLONE_CONFIG_HZ_PROVIDER="Other" +export RCLONE_CONFIG_HZ_ENDPOINT="$HETZNER_S3_ENDPOINT" +export RCLONE_CONFIG_HZ_ACCESS_KEY_ID="$AWS_ACCESS_KEY_ID" +export RCLONE_CONFIG_HZ_SECRET_ACCESS_KEY="$AWS_SECRET_ACCESS_KEY" + +# ============================================================================= +# GCLOUD RUN — create a Compute Engine VM, run the export there, then clean up +# ============================================================================= +if $GCLOUD_RUN; then + VM_NAME="${GCP_VM_NAME:-bd-export-vm}" + VM_ZONE="${GCP_VM_ZONE:-us-central1-a}" + SCRIPT_PATH="$(realpath "$0")" + ENV_PATH="$(dirname "$SCRIPT_PATH")/.env" + + log "==============================" + log " GCLOUD RUN MODE" + log "==============================" + + # ── Step 1/4: Create instance ─────────────────────────────────────────── + log "[1/4] Creating VM: $VM_NAME ($VM_ZONE) ..." + if gcloud compute instances describe "$VM_NAME" \ + --zone="$VM_ZONE" --project="$YOUR_PROJECT" &>/dev/null; then + log " VM already exists, reusing it." + else + gcloud compute instances create "$VM_NAME" \ + --project="$YOUR_PROJECT" \ + --zone="$VM_ZONE" \ + --machine-type=e2-standard-4 \ + --image-family=debian-12 \ + --image-project=debian-cloud \ + --boot-disk-size=20GB \ + --scopes=cloud-platform + log " VM created." + fi + + # ── Step 2/4: Wait for SSH + copy files ──────────────────────────────── + log "[2/4] Waiting for SSH and copying files..." + for i in {1..18}; do + if gcloud compute ssh "$VM_NAME" \ + --zone="$VM_ZONE" --project="$YOUR_PROJECT" \ + --command="echo ready" 2>/dev/null; then + break + fi + log " SSH not ready yet ($i/18), retrying in 10s..." + sleep 10 + done + + gcloud compute scp "$SCRIPT_PATH" "$ENV_PATH" \ + "$VM_NAME:~/" \ + --zone="$VM_ZONE" \ + --project="$YOUR_PROJECT" + log " Files copied." + + # ── Step 3/4: Install dependencies ───────────────────────────────────── + log "[3/4] Installing dependencies on VM (~2 min)..." + gcloud compute ssh "$VM_NAME" \ + --zone="$VM_ZONE" \ + --project="$YOUR_PROJECT" \ + --command="bash -s" <<'REMOTE_SETUP' +set -euo pipefail +export DEBIAN_FRONTEND=noninteractive +sudo apt-get update -qq +sudo apt-get install -y apt-transport-https ca-certificates gnupg curl parallel rclone +curl -fsSL https://packages.cloud.google.com/apt/doc/apt-key.gpg \ + | sudo gpg --dearmor -o /usr/share/keyrings/cloud.google.gpg +echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" \ + | sudo tee /etc/apt/sources.list.d/google-cloud-sdk.list >/dev/null +sudo apt-get update -qq +sudo apt-get install -y google-cloud-cli +chmod +x ~/roda.sh +echo "Dependencies installed." +REMOTE_SETUP + log " Dependencies ready." + + # ── Step 4/4: Run the export script interactively ────────────────────── + log "[4/4] Launching roda.sh on VM — answer prompts as they appear." + gcloud compute ssh "$VM_NAME" \ + --zone="$VM_ZONE" \ + --project="$YOUR_PROJECT" \ + -- bash ~/roda.sh + + # ── Cleanup: Delete VM ────────────────────────────────────────────────── + echo "" + echo "============================================================" + echo " CLEANUP" + echo "============================================================" + read -rp "Delete VM instance $VM_NAME? [y/N] " del_vm + if [[ "$del_vm" =~ ^[Yy]$ ]]; then + log "Deleting VM $VM_NAME ..." + gcloud compute instances delete "$VM_NAME" \ + --zone="$VM_ZONE" \ + --project="$YOUR_PROJECT" \ + --quiet + log "VM deleted." + else + log "VM kept. To delete manually:" + log " gcloud compute instances delete $VM_NAME --zone=$VM_ZONE --project=$YOUR_PROJECT" + fi + + exit 0 +fi + +# ----------------------------------------------------------------------------- +# STEP 1 — Create GCS bucket in US region (same as basedosdados) +# ----------------------------------------------------------------------------- +if $DRY_RUN; then + log "[DRY RUN] Would create GCS bucket: gs://$BUCKET_NAME in region $BUCKET_REGION" +else + log "Creating GCS bucket: gs://$BUCKET_NAME in region $BUCKET_REGION" + if gsutil ls "gs://$BUCKET_NAME" &>/dev/null; then + log "Bucket already exists, skipping creation." + else + gsutil mb \ + -p "$YOUR_PROJECT" \ + -l "$BUCKET_REGION" \ + -b on \ + "gs://$BUCKET_NAME" + log "Bucket created: gs://$BUCKET_NAME" + fi + +fi + +# Resume support: load already-done tables/transfers +touch "$DONE_FILE" "$FAILED_FILE" "$DONE_TRANSFERS_FILE" + +# ----------------------------------------------------------------------------- +# STEP 2 — Build the full table list from the basedosdados project +# +# We auto-discover all datasets and tables via the BQ API so we don't rely +# on a hardcoded list. This also detects any new tables added since the +# tables-summary.md was written. +# +# Atomicity: we write to a .tmp file and mv it into place only on success, +# so an interrupted run never leaves a partial list behind. +# ----------------------------------------------------------------------------- +log "Discovering all datasets in project: $SOURCE_PROJECT ..." +TABLE_LIST_FILE="all_tables.txt" +TABLE_LIST_TMP="${TABLE_LIST_FILE}.tmp" + +if [[ ! -f "$TABLE_LIST_FILE" ]]; then + bq ls --project_id="$SOURCE_PROJECT" --max_results=10000 --format=json 2>/dev/null \ + | python3 -c " +import json, sys +datasets = json.load(sys.stdin) +for ds in datasets: + print(ds['datasetReference']['datasetId']) +" > /tmp/datasets.txt + + log "Found $(wc -l < /tmp/datasets.txt) datasets. Listing tables in parallel..." + + TMP_TABLE_DIR=$(mktemp -d) + + list_dataset_tables() { + local dataset="$1" + local source="$2" + local tmp_dir="$3" + bq ls \ + --project_id="$source" \ + --dataset_id="$source:$dataset" \ + --max_results=10000 \ + --format=json 2>/dev/null \ + | python3 -c " +import json, sys +data = sys.stdin.read() +if not data.strip(): + sys.exit(0) +for t in json.loads(data): + ref = t.get('tableReference', {}) + if t.get('type') in ('TABLE', 'EXTERNAL'): + print(ref['datasetId'] + '.' + ref['tableId']) +" > "$tmp_dir/$dataset.txt" + } + export -f list_dataset_tables + + parallel --jobs 16 list_dataset_tables {} "$SOURCE_PROJECT" "$TMP_TABLE_DIR" < /tmp/datasets.txt + + cat "$TMP_TABLE_DIR"/*.txt | sort > "$TABLE_LIST_TMP" + rm -rf "$TMP_TABLE_DIR" + mv "$TABLE_LIST_TMP" "$TABLE_LIST_FILE" + log "Total tables discovered: $(wc -l < "$TABLE_LIST_FILE")" +else + log "Reusing existing table list: $TABLE_LIST_FILE ($(wc -l < "$TABLE_LIST_FILE") tables)" +fi + +# ----------------------------------------------------------------------------- +# DRY RUN — show table count and exit +# ----------------------------------------------------------------------------- +if $DRY_RUN; then + TOTAL=$(wc -l < "$TABLE_LIST_FILE") + log "[DRY RUN] $TOTAL tables found. No exports will run." + log "[DRY RUN] Estimating total size via bq show in parallel (this may take a while)..." + + get_table_bytes() { + local table="$1" + local source="$2" + local dataset table_id + dataset=$(echo "$table" | cut -d. -f1) + table_id=$(echo "$table" | cut -d. -f2) + bq show --format=json "${source}:${dataset}.${table_id}" 2>/dev/null \ + | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('numBytes','0'))" 2>/dev/null \ + || echo 0 + } + export -f get_table_bytes + + TOTAL_BYTES=$(parallel --jobs 16 get_table_bytes {} "$SOURCE_PROJECT" < "$TABLE_LIST_FILE" \ + | awk '{s+=$1} END{print s+0}') + + TOTAL_GB=$(echo "scale=2; $TOTAL_BYTES / 1073741824" | bc) + # Parquet+zstd typically compresses structured data 5–10x vs BigQuery's raw numBytes + COMPRESSED_LOW=$(echo "scale=2; $TOTAL_GB / 10" | bc) + COMPRESSED_HIGH=$(echo "scale=2; $TOTAL_GB / 5" | bc) + EGRESS_LOW=$(echo "scale=2; $COMPRESSED_LOW * 0.08" | bc) + EGRESS_HIGH=$(echo "scale=2; $COMPRESSED_HIGH * 0.12" | bc) + log "[DRY RUN] BigQuery raw size (uncompressed): ~${TOTAL_GB} GB" + log "[DRY RUN] Estimated Parquet+zstd size: ~${COMPRESSED_LOW}–${COMPRESSED_HIGH} GB" + log "[DRY RUN] Estimated GCS→Hetzner egress cost: USD ${EGRESS_LOW}–${EGRESS_HIGH}" + log "[DRY RUN] Done. Remove --dry-run to start the actual export." + exit 0 +fi + +# ----------------------------------------------------------------------------- +# COST WARNING — confirm before starting export +# ----------------------------------------------------------------------------- +echo "" +echo "============================================================" +echo " COST WARNING" +echo " Transferring data from GCS to Hetzner costs ~\$0.08-0.12/GB" +echo " in internet egress fees charged to: $YOUR_PROJECT" +echo " Run with --dry-run first to estimate the total size." +echo "============================================================" +echo "" +read -rp "Press ENTER to start the export, or Ctrl+C to abort: " + +# ----------------------------------------------------------------------------- +# STEP 3 — Export function (called in parallel) +# ----------------------------------------------------------------------------- +export_table() { + local table="$1" + local bucket="$2" + local project="$3" + local source="$4" + local done_file="$5" + local failed_file="$6" + local log_file="$7" + + # Skip if already done + if grep -qxF "$table" "$done_file" 2>/dev/null; then + echo "[SKIP] $table (already exported)" >> "$log_file" + return 0 + fi + + local dataset table_id gcs_prefix + dataset=$(echo "$table" | cut -d. -f1) + table_id=$(echo "$table" | cut -d. -f2) + gcs_prefix="gs://$bucket/$dataset/$table_id" + + echo "[START] Exporting $source:$table → $gcs_prefix/*.parquet" >> "$log_file" + + # Run bq extract with retry (up to 3 attempts) + # Skip retries immediately if the error is a known incompatible type + local attempt=0 + local success=false + local output + while [[ $attempt -lt 3 ]]; do + attempt=$((attempt + 1)) + output=$(bq extract \ + --project_id="$project" \ + --destination_format=PARQUET \ + --compression=ZSTD \ + --location=US \ + "${source}:${dataset}.${table_id}" \ + "${gcs_prefix}/*.parquet" \ + 2>&1) + local exit_code=$? + echo "$output" >> "$log_file" + + if [[ $exit_code -eq 0 ]]; then + success=true + break + fi + + # Detect permanently incompatible types — no point retrying + if echo "$output" | grep -qi "not supported\|unsupported type\|GEOGRAPHY\|JSON type"; then + echo "[SKIP_INCOMPATIBLE] $table — unsupported column type, skipping retries" >> "$log_file" + flock "$failed_file" bash -c "echo '[INCOMPATIBLE] $table' >> '$failed_file'" + return 0 + fi + + # Detect access/permission errors — no point retrying + if echo "$output" | grep -qi "access denied\|permission denied\|not authorized\|403\|does not exist\|Not found"; then + echo "[SKIP_ACCESS] $table — access denied or not found, skipping retries" >> "$log_file" + flock "$failed_file" bash -c "echo '[ACCESS_DENIED] $table' >> '$failed_file'" + return 0 + fi + + echo "[RETRY $attempt/3] $table" >> "$log_file" + sleep $((attempt * 10)) + done + + if $success; then + # flock prevents race condition when multiple workers write concurrently + flock "$done_file" bash -c "echo '$table' >> '$done_file'" + echo "[DONE] $table" >> "$log_file" + else + flock "$failed_file" bash -c "echo '$table' >> '$failed_file'" + echo "[FAIL] $table after 3 attempts" >> "$log_file" + fi +} + +export -f export_table + +# ----------------------------------------------------------------------------- +# STEP 4 — Run exports in parallel +# ----------------------------------------------------------------------------- +log "Starting parallel exports ($PARALLEL_EXPORTS workers)..." +log "Progress is logged to: $LOG_FILE" +log "Failed tables will be written to: $FAILED_FILE" + +# Filter out already-done tables +comm -23 \ + <(sort "$TABLE_LIST_FILE") \ + <(sort "$DONE_FILE") \ +| parallel \ + --jobs "$PARALLEL_EXPORTS" \ + --progress \ + --bar \ + export_table {} "$BUCKET_NAME" "$YOUR_PROJECT" "$SOURCE_PROJECT" \ + "$DONE_FILE" "$FAILED_FILE" "$LOG_FILE" \ +|| true # failures are tracked in $FAILED_FILE; don't let parallel's exit code abort the script + +TOTAL=$(wc -l < "$TABLE_LIST_FILE") +DONE=$(wc -l < "$DONE_FILE") +FAILED=$(wc -l < "$FAILED_FILE") +log "Export phase complete: $DONE/$TOTAL done, $FAILED failed" + +if [[ $FAILED -gt 0 ]]; then + log "Failed tables:" + cat "$FAILED_FILE" | tee -a "$LOG_FILE" + log "To retry failed tables only, run: bash $0 --retry-failed" +fi + +# ----------------------------------------------------------------------------- +# STEP 5 — Transfer GCS → Hetzner Object Storage via rclone (no local staging) +# +# rclone streams data directly between GCS and S3 through RAM only — +# no local disk required. +# ----------------------------------------------------------------------------- +log "Starting transfer to Hetzner Object Storage ($HETZNER_S3_ENDPOINT)..." + +TRANSFER_LOG_DIR=$(mktemp -d) + +# Compute total datasets in GCS bucket once (used for progress display) +TRANSFER_TOTAL=$(gsutil ls "gs://$BUCKET_NAME/" | wc -l) +export TRANSFER_TOTAL + +download_dataset() { + local dataset="$1" + local bucket="$2" + local s3_bucket="$3" + local s3_concurrency="$4" + local done_transfers_file="$5" + local log_dir="$6" + local total="$7" + local dataset_log="$log_dir/${dataset}.log" + + # Resume: skip datasets already transferred + if grep -qxF "$dataset" "$done_transfers_file" 2>/dev/null; then + echo "[SKIP_TRANSFER] $dataset (already transferred)" > "$dataset_log" + return 0 + fi + + echo "[TRANSFER] gs://$bucket/$dataset/ → hz:$s3_bucket/$dataset/" > "$dataset_log" + + # Named remotes bd: (GCS) and hz: (Hetzner S3) are configured via RCLONE_CONFIG_* env vars + if rclone copy \ + "bd:$bucket/$dataset/" \ + "hz:$s3_bucket/$dataset/" \ + --transfers "$s3_concurrency" \ + --s3-upload-concurrency "$s3_concurrency" \ + --progress \ + >> "$dataset_log" 2>&1; then + flock "$done_transfers_file" bash -c "echo '$dataset' >> '$done_transfers_file'" + echo "[TRANSFERRED] $dataset" >> "$dataset_log" + local done_count + done_count=$(wc -l < "$done_transfers_file") + local pct=$(( done_count * 100 / total )) + echo "[${done_count}/${total}] ${pct}% datasets transferidos" + else + echo "[TRANSFER FAIL] rclone failed for $dataset" >> "$dataset_log" + return 1 + fi +} + +export -f download_dataset + +# Get list of exported datasets, skipping already-transferred ones +comm -23 \ + <(gsutil ls "gs://$BUCKET_NAME/" | sed 's|gs://[^/]*/||;s|/||' | sort -u) \ + <(sort "$DONE_TRANSFERS_FILE") \ +| parallel \ + --jobs "$PARALLEL_UPLOADS" \ + download_dataset {} "$BUCKET_NAME" "$HETZNER_S3_BUCKET" "$S3_CONCURRENCY" "$DONE_TRANSFERS_FILE" "$TRANSFER_LOG_DIR" "$TRANSFER_TOTAL" \ +|| true # failures are tracked per-dataset; don't abort + +# Merge per-dataset logs into main log in order +for f in $(ls "$TRANSFER_LOG_DIR"/*.log 2>/dev/null | sort); do + cat "$f" >> "$LOG_FILE" +done +rm -rf "$TRANSFER_LOG_DIR" + +log "Transfer complete." + +# ----------------------------------------------------------------------------- +# STEP 6 — Verify file counts on Hetzner Object Storage vs GCS +# ----------------------------------------------------------------------------- +log "Verifying file counts..." +GCS_COUNT=$(gsutil ls -r "gs://$BUCKET_NAME/**" | grep '\.parquet$' | wc -l) +S3_COUNT=$(rclone ls "hz:$HETZNER_S3_BUCKET" 2>/dev/null | grep '\.parquet$' | wc -l) + +log "GCS parquet files: $GCS_COUNT" +log "S3 parquet files: $S3_COUNT" + +if [[ "$GCS_COUNT" -eq "$S3_COUNT" ]]; then + log "File counts match. Transfer verified." +else + log_err "Count mismatch! GCS=$GCS_COUNT S3=$S3_COUNT" + log_err "Re-run the script to resume failed datasets or check $LOG_FILE for errors." +fi + +# ----------------------------------------------------------------------------- +# STEP 7 — Clean up GCS bucket to stop storage charges +# ----------------------------------------------------------------------------- +read -rp "Delete GCS bucket gs://$BUCKET_NAME to stop storage charges? [y/N] " confirm +if [[ "$confirm" =~ ^[Yy]$ ]]; then + log "Deleting bucket gs://$BUCKET_NAME ..." + gsutil -m rm -r "gs://$BUCKET_NAME" + gsutil rb "gs://$BUCKET_NAME" + log "Bucket deleted. Storage charges stopped." +else + log "Bucket kept. Remember to delete it later: gsutil -m rm -r gs://$BUCKET_NAME && gsutil rb gs://$BUCKET_NAME" +fi + +log "All done! Data is at s3://$HETZNER_S3_BUCKET/ ($HETZNER_S3_ENDPOINT)" +log "Total exported: $DONE tables | Failed: $FAILED tables" +log "See $LOG_FILE for full details."